summaryrefslogtreecommitdiff
path: root/frontends/cocoa/PSMTabBarControl
diff options
context:
space:
mode:
Diffstat (limited to 'frontends/cocoa/PSMTabBarControl')
-rw-r--r--frontends/cocoa/PSMTabBarControl/Images/AquaTabCloseDirty_Front.pngbin0 -> 292 bytes
-rw-r--r--frontends/cocoa/PSMTabBarControl/Images/AquaTabCloseDirty_Front_Pressed.pngbin0 -> 292 bytes
-rw-r--r--frontends/cocoa/PSMTabBarControl/Images/AquaTabCloseDirty_Front_Rollover.pngbin0 -> 297 bytes
-rw-r--r--frontends/cocoa/PSMTabBarControl/Images/AquaTabClose_Front.pngbin0 -> 307 bytes
-rw-r--r--frontends/cocoa/PSMTabBarControl/Images/AquaTabClose_Front_Pressed.pngbin0 -> 310 bytes
-rw-r--r--frontends/cocoa/PSMTabBarControl/Images/AquaTabClose_Front_Rollover.pngbin0 -> 317 bytes
-rw-r--r--frontends/cocoa/PSMTabBarControl/Images/AquaTabNew.pngbin0 -> 371 bytes
-rw-r--r--frontends/cocoa/PSMTabBarControl/Images/AquaTabNewPressed.pngbin0 -> 380 bytes
-rw-r--r--frontends/cocoa/PSMTabBarControl/Images/AquaTabNewRollover.pngbin0 -> 380 bytes
-rw-r--r--frontends/cocoa/PSMTabBarControl/Images/overflowImage.pngbin0 -> 256 bytes
-rw-r--r--frontends/cocoa/PSMTabBarControl/Images/overflowImagePressed.pngbin0 -> 250 bytes
-rw-r--r--frontends/cocoa/PSMTabBarControl/Images/pi.pngbin0 -> 564 bytes
-rw-r--r--frontends/cocoa/PSMTabBarControl/NSBezierPath_AMShading.h22
-rw-r--r--frontends/cocoa/PSMTabBarControl/NSBezierPath_AMShading.m126
-rw-r--r--frontends/cocoa/PSMTabBarControl/NSString_AITruncation.h12
-rw-r--r--frontends/cocoa/PSMTabBarControl/NSString_AITruncation.m34
-rw-r--r--frontends/cocoa/PSMTabBarControl/PSMOverflowPopUpButton.h27
-rw-r--r--frontends/cocoa/PSMTabBarControl/PSMOverflowPopUpButton.m157
-rw-r--r--frontends/cocoa/PSMTabBarControl/PSMProgressIndicator.h14
-rw-r--r--frontends/cocoa/PSMTabBarControl/PSMProgressIndicator.m43
-rw-r--r--frontends/cocoa/PSMTabBarControl/PSMRolloverButton.h28
-rw-r--r--frontends/cocoa/PSMTabBarControl/PSMRolloverButton.m180
-rw-r--r--frontends/cocoa/PSMTabBarControl/PSMTabBarCell.h115
-rw-r--r--frontends/cocoa/PSMTabBarControl/PSMTabBarCell.m536
-rw-r--r--frontends/cocoa/PSMTabBarControl/PSMTabBarControl+Private.h49
-rw-r--r--frontends/cocoa/PSMTabBarControl/PSMTabBarControl.h239
-rw-r--r--frontends/cocoa/PSMTabBarControl/PSMTabBarControl.m2035
-rw-r--r--frontends/cocoa/PSMTabBarControl/PSMTabBarController.h38
-rw-r--r--frontends/cocoa/PSMTabBarControl/PSMTabBarController.m648
-rw-r--r--frontends/cocoa/PSMTabBarControl/PSMTabDragAssistant.h101
-rw-r--r--frontends/cocoa/PSMTabBarControl/PSMTabDragAssistant.m850
-rw-r--r--frontends/cocoa/PSMTabBarControl/PSMTabDragView.h21
-rw-r--r--frontends/cocoa/PSMTabBarControl/PSMTabDragView.m60
-rw-r--r--frontends/cocoa/PSMTabBarControl/PSMTabDragWindow.h20
-rw-r--r--frontends/cocoa/PSMTabBarControl/PSMTabDragWindow.m51
-rw-r--r--frontends/cocoa/PSMTabBarControl/PSMTabDragWindowController.h33
-rw-r--r--frontends/cocoa/PSMTabBarControl/PSMTabDragWindowController.m109
-rw-r--r--frontends/cocoa/PSMTabBarControl/PSMTabStyle.h57
-rw-r--r--frontends/cocoa/PSMTabBarControl/PSMUnifiedTabStyle.h29
-rw-r--r--frontends/cocoa/PSMTabBarControl/PSMUnifiedTabStyle.m581
-rw-r--r--frontends/cocoa/PSMTabBarControl/ReadMe.rtfd/TXT.rtf186
-rw-r--r--frontends/cocoa/PSMTabBarControl/ReadMe.rtfd/startpage.gifbin0 -> 11246 bytes
42 files changed, 6401 insertions, 0 deletions
diff --git a/frontends/cocoa/PSMTabBarControl/Images/AquaTabCloseDirty_Front.png b/frontends/cocoa/PSMTabBarControl/Images/AquaTabCloseDirty_Front.png
new file mode 100644
index 000000000..77d220505
--- /dev/null
+++ b/frontends/cocoa/PSMTabBarControl/Images/AquaTabCloseDirty_Front.png
Binary files differ
diff --git a/frontends/cocoa/PSMTabBarControl/Images/AquaTabCloseDirty_Front_Pressed.png b/frontends/cocoa/PSMTabBarControl/Images/AquaTabCloseDirty_Front_Pressed.png
new file mode 100644
index 000000000..197ea95c2
--- /dev/null
+++ b/frontends/cocoa/PSMTabBarControl/Images/AquaTabCloseDirty_Front_Pressed.png
Binary files differ
diff --git a/frontends/cocoa/PSMTabBarControl/Images/AquaTabCloseDirty_Front_Rollover.png b/frontends/cocoa/PSMTabBarControl/Images/AquaTabCloseDirty_Front_Rollover.png
new file mode 100644
index 000000000..2dfe5777e
--- /dev/null
+++ b/frontends/cocoa/PSMTabBarControl/Images/AquaTabCloseDirty_Front_Rollover.png
Binary files differ
diff --git a/frontends/cocoa/PSMTabBarControl/Images/AquaTabClose_Front.png b/frontends/cocoa/PSMTabBarControl/Images/AquaTabClose_Front.png
new file mode 100644
index 000000000..02b72d39e
--- /dev/null
+++ b/frontends/cocoa/PSMTabBarControl/Images/AquaTabClose_Front.png
Binary files differ
diff --git a/frontends/cocoa/PSMTabBarControl/Images/AquaTabClose_Front_Pressed.png b/frontends/cocoa/PSMTabBarControl/Images/AquaTabClose_Front_Pressed.png
new file mode 100644
index 000000000..f81125a01
--- /dev/null
+++ b/frontends/cocoa/PSMTabBarControl/Images/AquaTabClose_Front_Pressed.png
Binary files differ
diff --git a/frontends/cocoa/PSMTabBarControl/Images/AquaTabClose_Front_Rollover.png b/frontends/cocoa/PSMTabBarControl/Images/AquaTabClose_Front_Rollover.png
new file mode 100644
index 000000000..4f6b865f7
--- /dev/null
+++ b/frontends/cocoa/PSMTabBarControl/Images/AquaTabClose_Front_Rollover.png
Binary files differ
diff --git a/frontends/cocoa/PSMTabBarControl/Images/AquaTabNew.png b/frontends/cocoa/PSMTabBarControl/Images/AquaTabNew.png
new file mode 100644
index 000000000..10a83705d
--- /dev/null
+++ b/frontends/cocoa/PSMTabBarControl/Images/AquaTabNew.png
Binary files differ
diff --git a/frontends/cocoa/PSMTabBarControl/Images/AquaTabNewPressed.png b/frontends/cocoa/PSMTabBarControl/Images/AquaTabNewPressed.png
new file mode 100644
index 000000000..cb4dd10f2
--- /dev/null
+++ b/frontends/cocoa/PSMTabBarControl/Images/AquaTabNewPressed.png
Binary files differ
diff --git a/frontends/cocoa/PSMTabBarControl/Images/AquaTabNewRollover.png b/frontends/cocoa/PSMTabBarControl/Images/AquaTabNewRollover.png
new file mode 100644
index 000000000..4d469f8a6
--- /dev/null
+++ b/frontends/cocoa/PSMTabBarControl/Images/AquaTabNewRollover.png
Binary files differ
diff --git a/frontends/cocoa/PSMTabBarControl/Images/overflowImage.png b/frontends/cocoa/PSMTabBarControl/Images/overflowImage.png
new file mode 100644
index 000000000..2b762555d
--- /dev/null
+++ b/frontends/cocoa/PSMTabBarControl/Images/overflowImage.png
Binary files differ
diff --git a/frontends/cocoa/PSMTabBarControl/Images/overflowImagePressed.png b/frontends/cocoa/PSMTabBarControl/Images/overflowImagePressed.png
new file mode 100644
index 000000000..b3918b344
--- /dev/null
+++ b/frontends/cocoa/PSMTabBarControl/Images/overflowImagePressed.png
Binary files differ
diff --git a/frontends/cocoa/PSMTabBarControl/Images/pi.png b/frontends/cocoa/PSMTabBarControl/Images/pi.png
new file mode 100644
index 000000000..4d598dc73
--- /dev/null
+++ b/frontends/cocoa/PSMTabBarControl/Images/pi.png
Binary files differ
diff --git a/frontends/cocoa/PSMTabBarControl/NSBezierPath_AMShading.h b/frontends/cocoa/PSMTabBarControl/NSBezierPath_AMShading.h
new file mode 100644
index 000000000..9aa17a905
--- /dev/null
+++ b/frontends/cocoa/PSMTabBarControl/NSBezierPath_AMShading.h
@@ -0,0 +1,22 @@
+//
+// NSBezierPath_AMShading.h
+// ------------------------
+//
+// Created by Andreas on 2005-06-01.
+// Copyright 2005 Andreas Mayer. All rights reserved.
+//
+// based on http://www.cocoadev.com/index.pl?GradientFill
+
+#import <Cocoa/Cocoa.h>
+
+@interface NSBezierPath (AMShading)
+
+- (void)customHorizontalFillWithCallbacks:(CGFunctionCallbacks)functionCallbacks firstColor:(NSColor *)firstColor secondColor:(NSColor *)secondColor;
+- (void)customVerticalFillWithCallbacks:(CGFunctionCallbacks)functionCallbacks firstColor:(NSColor *)firstColor secondColor:(NSColor *)secondColor;
+
+- (void)linearGradientFillWithStartColor:(NSColor *)startColor endColor:(NSColor *)endColor;
+- (void)linearVerticalGradientFillWithStartColor:(NSColor *)startColor endColor:(NSColor *)endColor;
+
+- (void)bilinearGradientFillWithOuterColor:(NSColor *)outerColor innerColor:(NSColor *)innerColor;
+
+@end
diff --git a/frontends/cocoa/PSMTabBarControl/NSBezierPath_AMShading.m b/frontends/cocoa/PSMTabBarControl/NSBezierPath_AMShading.m
new file mode 100644
index 000000000..dbd180599
--- /dev/null
+++ b/frontends/cocoa/PSMTabBarControl/NSBezierPath_AMShading.m
@@ -0,0 +1,126 @@
+//
+// NSBezierPath_AMShading.m
+// ------------------------
+//
+// Created by Andreas on 2005-06-01.
+// Copyright 2005 Andreas Mayer. All rights reserved.
+//
+
+#import "NSBezierPath_AMShading.h"
+
+@implementation NSBezierPath (AMShading)
+
+static void linearShadedColor(void *info, const CGFloat *in, CGFloat *out)
+{
+ CGFloat *colors = (CGFloat *)info;
+ *out++ = colors[0] + *in * colors[8];
+ *out++ = colors[1] + *in * colors[9];
+ *out++ = colors[2] + *in * colors[10];
+ *out++ = colors[3] + *in * colors[11];
+}
+
+static void bilinearShadedColor(void *info, const CGFloat *in, CGFloat *out)
+{
+ CGFloat *colors = (CGFloat *)info;
+ CGFloat factor = (*in) * 2.0;
+ if (*in > 0.5) {
+ factor = 2 - factor;
+ }
+ *out++ = colors[0] + factor * colors[8];
+ *out++ = colors[1] + factor * colors[9];
+ *out++ = colors[2] + factor * colors[10];
+ *out++ = colors[3] + factor * colors[11];
+}
+
+- (void)linearGradientFillWithStartColor:(NSColor *)startColor endColor:(NSColor *)endColor
+{
+ static const CGFunctionCallbacks callbacks = { 0, &linearShadedColor, NULL };
+
+ [self customHorizontalFillWithCallbacks:callbacks firstColor:startColor secondColor:endColor];
+}
+
+- (void)linearVerticalGradientFillWithStartColor:(NSColor *)startColor endColor:(NSColor *)endColor
+{
+ static const CGFunctionCallbacks callbacks = { 0, &linearShadedColor, NULL };
+
+ [self customVerticalFillWithCallbacks:callbacks firstColor:startColor secondColor:endColor];
+}
+
+- (void)bilinearGradientFillWithOuterColor:(NSColor *)outerColor innerColor:(NSColor *)innerColor
+{
+ static const CGFunctionCallbacks callbacks = { 0, &bilinearShadedColor, NULL };
+
+ [self customHorizontalFillWithCallbacks:callbacks firstColor:innerColor secondColor:outerColor];
+}
+
+- (void)customFillWithCallbacks:(CGFunctionCallbacks)functionCallbacks firstColor:(NSColor *)firstColor secondColor:(NSColor *)secondColor startPoint:(CGPoint)startPoint endPoint:(CGPoint)endPoint
+{
+ CGColorSpaceRef colorspace;
+ CGShadingRef shading;
+ CGFunctionRef function;
+ CGFloat colors[12]; // pointer to color values
+
+ // get my context
+ CGContextRef currentContext = (CGContextRef)[[NSGraphicsContext currentContext] graphicsPort];
+
+ NSColor *deviceDependentFirstColor = [firstColor colorUsingColorSpaceName:NSDeviceRGBColorSpace];
+ NSColor *deviceDependentSecondColor = [secondColor colorUsingColorSpaceName:NSDeviceRGBColorSpace];
+
+ // set up colors for gradient
+ colors[0] = [deviceDependentFirstColor redComponent];
+ colors[1] = [deviceDependentFirstColor greenComponent];
+ colors[2] = [deviceDependentFirstColor blueComponent];
+ colors[3] = [deviceDependentFirstColor alphaComponent];
+
+ colors[4] = [deviceDependentSecondColor redComponent];
+ colors[5] = [deviceDependentSecondColor greenComponent];
+ colors[6] = [deviceDependentSecondColor blueComponent];
+ colors[7] = [deviceDependentSecondColor alphaComponent];
+
+ // difference between start and end color for each color components
+ colors[8] = (colors[4] - colors[0]);
+ colors[9] = (colors[5] - colors[1]);
+ colors[10] = (colors[6] - colors[2]);
+ colors[11] = (colors[7] - colors[3]);
+
+ // draw gradient
+ colorspace = CGColorSpaceCreateDeviceRGB();
+ size_t components = 1 + CGColorSpaceGetNumberOfComponents(colorspace);
+ static const CGFloat domain[2] = { 0.0, 1.0 };
+ static const CGFloat range[10] = { 0, 1, 0, 1, 0, 1, 0, 1, 0, 1 };
+ //static const CGFunctionCallbacks callbacks = {0, &bilinearShadedColor, NULL};
+
+ // Create a CGFunctionRef that describes a function taking 1 input and kChannelsPerColor outputs.
+ function = CGFunctionCreate(colors, 1, domain, components, range, &functionCallbacks);
+
+ shading = CGShadingCreateAxial(colorspace, startPoint, endPoint, function, NO, NO);
+
+ CGContextSaveGState(currentContext);
+ [self addClip];
+ CGContextDrawShading(currentContext, shading);
+ CGContextRestoreGState(currentContext);
+
+ CGShadingRelease(shading);
+ CGFunctionRelease(function);
+ CGColorSpaceRelease(colorspace);
+}
+
+- (void)customHorizontalFillWithCallbacks:(CGFunctionCallbacks)functionCallbacks firstColor:(NSColor *)firstColor secondColor:(NSColor *)secondColor
+{
+ [self customFillWithCallbacks:functionCallbacks
+ firstColor:firstColor
+ secondColor:secondColor
+ startPoint:CGPointMake(0, NSMinY([self bounds]))
+ endPoint:CGPointMake(0, NSMaxY([self bounds]))];
+}
+
+- (void)customVerticalFillWithCallbacks:(CGFunctionCallbacks)functionCallbacks firstColor:(NSColor *)firstColor secondColor:(NSColor *)secondColor
+{
+ [self customFillWithCallbacks:functionCallbacks
+ firstColor:firstColor
+ secondColor:secondColor
+ startPoint:CGPointMake(NSMinX([self bounds]), 0)
+ endPoint:CGPointMake(NSMaxX([self bounds]), 0)];
+}
+
+@end
diff --git a/frontends/cocoa/PSMTabBarControl/NSString_AITruncation.h b/frontends/cocoa/PSMTabBarControl/NSString_AITruncation.h
new file mode 100644
index 000000000..cbcbf2c7f
--- /dev/null
+++ b/frontends/cocoa/PSMTabBarControl/NSString_AITruncation.h
@@ -0,0 +1,12 @@
+//
+// NSString_AITruncation.h
+// PSMTabBarControl
+//
+// Created by Evan Schoenberg on 7/14/07.
+//
+
+#import <Cocoa/Cocoa.h>
+
+@interface NSString (AITruncation)
+- (NSString *)stringWithEllipsisByTruncatingToLength:(NSUInteger)length;
+@end
diff --git a/frontends/cocoa/PSMTabBarControl/NSString_AITruncation.m b/frontends/cocoa/PSMTabBarControl/NSString_AITruncation.m
new file mode 100644
index 000000000..9d9ce6189
--- /dev/null
+++ b/frontends/cocoa/PSMTabBarControl/NSString_AITruncation.m
@@ -0,0 +1,34 @@
+//
+// NSString_AITruncation.m
+// PSMTabBarControl
+//
+// Created by Evan Schoenberg on 7/14/07.
+// From Adium, which is licensed under the GPL. Used in PSMTabBarControl with permission.
+// The contents of this remain licensed under the GPL.
+//
+
+#import "NSString_AITruncation.h"
+
+@implementation NSString (AITruncation)
+
++ (id)ellipsis
+{
+ return [NSString stringWithUTF8String:"\xE2\x80\xA6"];
+}
+
+- (NSString *)stringWithEllipsisByTruncatingToLength:(NSUInteger)length
+{
+ NSString *returnString;
+
+ if (length < [self length]) {
+ //Truncate and append the ellipsis
+ returnString = [[self substringToIndex:length - 1] stringByAppendingString:[NSString ellipsis]];
+ } else {
+ //We don't need to truncate, so don't append an ellipsis
+ returnString = [self copy];
+ }
+
+ return returnString;
+}
+
+@end
diff --git a/frontends/cocoa/PSMTabBarControl/PSMOverflowPopUpButton.h b/frontends/cocoa/PSMTabBarControl/PSMOverflowPopUpButton.h
new file mode 100644
index 000000000..0c39f32e0
--- /dev/null
+++ b/frontends/cocoa/PSMTabBarControl/PSMOverflowPopUpButton.h
@@ -0,0 +1,27 @@
+//
+// PSMOverflowPopUpButton.h
+// PSMTabBarControl
+//
+// Created by John Pannell on 11/4/05.
+// Copyright 2005 Positive Spin Media. All rights reserved.
+//
+
+#import <Cocoa/Cocoa.h>
+
+@interface PSMOverflowPopUpButton : NSPopUpButton {
+ NSImage *_PSMTabBarOverflowPopUpImage;
+ NSImage *_PSMTabBarOverflowDownPopUpImage;
+ BOOL _down;
+ BOOL _animatingAlternateImage;
+ NSTimer *_animationTimer;
+ CGFloat _animationValue;
+}
+
+//alternate image display
+- (BOOL)animatingAlternateImage;
+- (void)setAnimatingAlternateImage:(BOOL)flag;
+
+// archiving
+- (void)encodeWithCoder:(NSCoder *)aCoder;
+- (id)initWithCoder:(NSCoder *)aDecoder;
+@end
diff --git a/frontends/cocoa/PSMTabBarControl/PSMOverflowPopUpButton.m b/frontends/cocoa/PSMTabBarControl/PSMOverflowPopUpButton.m
new file mode 100644
index 000000000..d589227c5
--- /dev/null
+++ b/frontends/cocoa/PSMTabBarControl/PSMOverflowPopUpButton.m
@@ -0,0 +1,157 @@
+//
+// PSMOverflowPopUpButton.m
+// PSMTabBarControl
+//
+// Created by John Pannell on 11/4/05.
+// Copyright 2005 Positive Spin Media. All rights reserved.
+//
+
+#import <math.h>
+#import "PSMOverflowPopUpButton.h"
+#import "PSMTabBarControl.h"
+
+#define TIMER_INTERVAL 1.0 / 15.0
+#define ANIMATION_STEP 0.033f
+
+@implementation PSMOverflowPopUpButton
+
+- (id)initWithFrame:(NSRect)frameRect pullsDown:(BOOL)flag
+{
+ if ((self = [super initWithFrame:frameRect pullsDown:YES]) != nil) {
+ [self setBezelStyle:NSRegularSquareBezelStyle];
+ [self setBordered:NO];
+ [self setTitle:@""];
+ [self setPreferredEdge:NSMaxXEdge];
+ _PSMTabBarOverflowPopUpImage = [[NSImage alloc] initByReferencingFile:[[PSMTabBarControl bundle] pathForImageResource:@"overflowImage"]];
+ _PSMTabBarOverflowDownPopUpImage = [[NSImage alloc] initByReferencingFile:[[PSMTabBarControl bundle] pathForImageResource:@"overflowImagePressed"]];
+ _animatingAlternateImage = NO;
+ }
+ return self;
+}
+
+- (void)drawRect:(NSRect)rect
+{
+ if (_PSMTabBarOverflowPopUpImage == nil) {
+ [super drawRect:rect];
+ return;
+ }
+
+ NSImage *image = (_down) ? _PSMTabBarOverflowDownPopUpImage : _PSMTabBarOverflowPopUpImage;
+ NSSize imageSize = [image size];
+ NSRect bounds = [self bounds];
+
+ NSPoint drawPoint = NSMakePoint(NSMidX(bounds) - (imageSize.width * 0.5f), NSMidY(bounds) - (imageSize.height * 0.5f));
+
+ if ([self isFlipped]) {
+ drawPoint.y += imageSize.height;
+ }
+
+ [image drawAtPoint:drawPoint fromRect:CGRectZero operation:NSCompositingOperationSourceOver fraction:(_animatingAlternateImage ? 0.7f : 1.0f)];
+
+ if (_animatingAlternateImage) {
+ NSImage *alternateImage = [self alternateImage];
+ NSSize altImageSize = [alternateImage size];
+ drawPoint = NSMakePoint(NSMidX(bounds) - (altImageSize.width * 0.5f), NSMidY(bounds) - (altImageSize.height * 0.5f));
+
+ if ([self isFlipped]) {
+ drawPoint.y += altImageSize.height;
+ }
+
+ [[self alternateImage] drawAtPoint:drawPoint fromRect:CGRectZero operation:NSCompositingOperationSourceOver fraction:sin(_animationValue * M_PI)];
+ }
+}
+
+- (void)mouseDown:(NSEvent *)event
+{
+ _down = YES;
+ [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(notificationReceived:) name:NSMenuDidEndTrackingNotification object:[self menu]];
+ [self setNeedsDisplay:YES];
+ [super mouseDown:event];
+}
+
+- (void)setHidden:(BOOL)value
+{
+ if ([self isHidden] != value) {
+ if (value) {
+ // Stop any animating alternate image if we hide
+ [_animationTimer invalidate], _animationTimer = nil;
+ } else if (_animatingAlternateImage) {
+ // Restart any animating alternate image if we unhide
+ _animationValue = ANIMATION_STEP;
+ _animationTimer = [NSTimer scheduledTimerWithTimeInterval:TIMER_INTERVAL target:self selector:@selector(animateStep:) userInfo:nil repeats:YES];
+ [[NSRunLoop currentRunLoop] addTimer:_animationTimer forMode:NSEventTrackingRunLoopMode];
+ }
+ }
+
+ [super setHidden:value];
+}
+
+- (void)notificationReceived:(NSNotification *)notification
+{
+ _down = NO;
+ [self setNeedsDisplay:YES];
+ [[NSNotificationCenter defaultCenter] removeObserver:self];
+}
+
+- (void)setAnimatingAlternateImage:(BOOL)flag
+{
+ if (_animatingAlternateImage != flag) {
+ _animatingAlternateImage = flag;
+
+ if (![self isHidden]) {
+ if (flag) {
+ _animationValue = ANIMATION_STEP;
+ _animationTimer = [NSTimer scheduledTimerWithTimeInterval:TIMER_INTERVAL target:self selector:@selector(animateStep:) userInfo:nil repeats:YES];
+ [[NSRunLoop currentRunLoop] addTimer:_animationTimer forMode:NSEventTrackingRunLoopMode];
+ } else {
+ [_animationTimer invalidate], _animationTimer = nil;
+ }
+
+ [self setNeedsDisplay:YES];
+ }
+ }
+}
+
+- (BOOL)animatingAlternateImage
+{
+ return _animatingAlternateImage;
+}
+
+- (void)animateStep:(NSTimer *)timer
+{
+ _animationValue += ANIMATION_STEP;
+
+ if (_animationValue >= 1) {
+ _animationValue = ANIMATION_STEP;
+ }
+
+ [self setNeedsDisplay:YES];
+}
+
+#pragma mark -
+#pragma mark Archiving
+
+- (void)encodeWithCoder:(NSCoder *)aCoder
+{
+ [super encodeWithCoder:aCoder];
+ if ([aCoder allowsKeyedCoding]) {
+ [aCoder encodeObject:_PSMTabBarOverflowPopUpImage forKey:@"PSMTabBarOverflowPopUpImage"];
+ [aCoder encodeObject:_PSMTabBarOverflowDownPopUpImage forKey:@"PSMTabBarOverflowDownPopUpImage"];
+ [aCoder encodeBool:_animatingAlternateImage forKey:@"PSMTabBarOverflowAnimatingAlternateImage"];
+ }
+}
+
+- (id)initWithCoder:(NSCoder *)aDecoder
+{
+ if ((self = [super initWithCoder:aDecoder])) {
+ if ([aDecoder allowsKeyedCoding]) {
+ _PSMTabBarOverflowPopUpImage =
+ [aDecoder decodeObjectForKey:@"PSMTabBarOverflowPopUpImage"];
+ _PSMTabBarOverflowDownPopUpImage = [aDecoder decodeObjectForKey:@"PSMTabBarOverflowDownPopUpImage"];
+ [self setAnimatingAlternateImage:[aDecoder decodeBoolForKey:@"PSMTabBarOverflowAnimatingAlternateImage"]];
+ }
+ }
+ return self;
+}
+
+@end
diff --git a/frontends/cocoa/PSMTabBarControl/PSMProgressIndicator.h b/frontends/cocoa/PSMTabBarControl/PSMProgressIndicator.h
new file mode 100644
index 000000000..75c81d33c
--- /dev/null
+++ b/frontends/cocoa/PSMTabBarControl/PSMProgressIndicator.h
@@ -0,0 +1,14 @@
+//
+// PSMProgressIndicator.h
+// PSMTabBarControl
+//
+// Created by John Pannell on 2/23/06.
+// Copyright 2006 Positive Spin Media. All rights reserved.
+//
+
+#import <Cocoa/Cocoa.h>
+
+@interface PSMProgressIndicator : NSProgressIndicator {
+}
+
+@end \ No newline at end of file
diff --git a/frontends/cocoa/PSMTabBarControl/PSMProgressIndicator.m b/frontends/cocoa/PSMTabBarControl/PSMProgressIndicator.m
new file mode 100644
index 000000000..4532f31be
--- /dev/null
+++ b/frontends/cocoa/PSMTabBarControl/PSMProgressIndicator.m
@@ -0,0 +1,43 @@
+//
+// PSMProgressIndicator.m
+// PSMTabBarControl
+//
+// Created by John Pannell on 2/23/06.
+// Copyright 2006 Positive Spin Media. All rights reserved.
+//
+
+#import "PSMProgressIndicator.h"
+#import "PSMTabBarControl.h"
+
+@interface PSMTabBarControl (PSMProgressIndicatorExtensions)
+
+- (void)update;
+
+@end
+
+@implementation PSMProgressIndicator
+
+- (id)initWithFrame:(NSRect)frameRect
+{
+ if ((self = [super initWithFrame:frameRect]) == nil)
+ return nil;
+ [self setControlSize:NSControlSizeSmall];
+ return self;
+}
+
+// overrides to make tab bar control re-layout things if status changes
+- (void)setHidden:(BOOL)flag
+{
+ [super setHidden:flag];
+ [(PSMTabBarControl *)[self superview] update];
+}
+
+- (void)stopAnimation:(id)sender
+{
+ [NSObject cancelPreviousPerformRequestsWithTarget:self
+ selector:@selector(startAnimation:)
+ object:nil];
+ [super stopAnimation:sender];
+}
+
+@end
diff --git a/frontends/cocoa/PSMTabBarControl/PSMRolloverButton.h b/frontends/cocoa/PSMTabBarControl/PSMRolloverButton.h
new file mode 100644
index 000000000..6f2afdb09
--- /dev/null
+++ b/frontends/cocoa/PSMTabBarControl/PSMRolloverButton.h
@@ -0,0 +1,28 @@
+//
+// PSMOverflowPopUpButton.h
+// NetScrape
+//
+// Created by John Pannell on 8/4/04.
+// Copyright 2004 Positive Spin Media. All rights reserved.
+//
+
+#import <Cocoa/Cocoa.h>
+
+@interface PSMRolloverButton : NSButton {
+ NSImage *_rolloverImage;
+ NSImage *_usualImage;
+ NSTrackingRectTag _myTrackingRectTag;
+}
+
+// the regular image
+- (void)setUsualImage:(NSImage *)newImage;
+- (NSImage *)usualImage;
+
+// the rollover image
+- (void)setRolloverImage:(NSImage *)newImage;
+- (NSImage *)rolloverImage;
+
+// tracking rect for mouse events
+- (void)addTrackingRect;
+- (void)removeTrackingRect;
+@end \ No newline at end of file
diff --git a/frontends/cocoa/PSMTabBarControl/PSMRolloverButton.m b/frontends/cocoa/PSMTabBarControl/PSMRolloverButton.m
new file mode 100644
index 000000000..65df6aa0f
--- /dev/null
+++ b/frontends/cocoa/PSMTabBarControl/PSMRolloverButton.m
@@ -0,0 +1,180 @@
+//
+// PSMOverflowPopUpButton.m
+// NetScrape
+//
+// Created by John Pannell on 8/4/04.
+// Copyright 2004 Positive Spin Media. All rights reserved.
+//
+
+#import "PSMRolloverButton.h"
+
+@implementation PSMRolloverButton
+
+- (void)awakeFromNib
+{
+ if ([[self superclass] instancesRespondToSelector:@selector(awakeFromNib)]) {
+ [super awakeFromNib];
+ }
+
+ [[NSNotificationCenter defaultCenter] addObserver:self
+ selector:@selector(rolloverFrameDidChange:)
+ name:NSViewFrameDidChangeNotification
+ object:self];
+ [self setPostsFrameChangedNotifications:YES];
+ [self resetCursorRects];
+
+ _myTrackingRectTag = -1;
+}
+
+- (void)dealloc
+{
+ [[NSNotificationCenter defaultCenter] removeObserver:self];
+
+ [self removeTrackingRect];
+}
+
+// the regular image
+- (void)setUsualImage:(NSImage *)newImage
+{
+ _usualImage = newImage;
+
+ [self setImage:_usualImage];
+}
+
+- (NSImage *)usualImage
+{
+ return _usualImage;
+}
+
+- (void)setRolloverImage:(NSImage *)newImage
+{
+ _rolloverImage = newImage;
+}
+
+- (NSImage *)rolloverImage
+{
+ return _rolloverImage;
+}
+
+//Remove old tracking rects when we change superviews
+- (void)viewWillMoveToSuperview:(NSView *)newSuperview
+{
+ [self removeTrackingRect];
+
+ [super viewWillMoveToSuperview:newSuperview];
+}
+
+- (void)viewDidMoveToSuperview
+{
+ [super viewDidMoveToSuperview];
+
+ [self resetCursorRects];
+}
+
+- (void)viewWillMoveToWindow:(NSWindow *)newWindow
+{
+ [self removeTrackingRect];
+
+ [super viewWillMoveToWindow:newWindow];
+}
+
+- (void)viewDidMoveToWindow
+{
+ [super viewDidMoveToWindow];
+
+ [self resetCursorRects];
+}
+
+- (void)rolloverFrameDidChange:(NSNotification *)inNotification
+{
+ [self resetCursorRects];
+}
+
+- (void)addTrackingRect
+{
+ // assign a tracking rect to watch for mouse enter/exit
+ NSRect trackRect = [self bounds];
+ NSPoint localPoint = [self convertPoint:NSPointFromCGPoint([self.window convertRectFromScreen:(NSRect){.origin = NSEvent.mouseLocation, .size = CGSizeMake(1, 1) }].origin) fromView:nil];
+ BOOL mouseInside = NSPointInRect(localPoint, trackRect);
+
+ _myTrackingRectTag = [self addTrackingRect:trackRect owner:self userData:nil assumeInside:mouseInside];
+ if (mouseInside) {
+ //[self mouseEntered:nil];
+ [self setImage:_rolloverImage];
+ } else {
+ //[self mouseExited:nil];
+ [self setImage:_usualImage];
+ }
+}
+
+- (void)removeTrackingRect
+{
+ if (_myTrackingRectTag != -1) {
+ [self removeTrackingRect:_myTrackingRectTag];
+ }
+ _myTrackingRectTag = -1;
+}
+
+// override for rollover effect
+- (void)mouseEntered:(NSEvent *)theEvent
+{
+ // set rollover image
+ [self setImage:_rolloverImage];
+
+ [super mouseEntered:theEvent];
+}
+
+- (void)mouseExited:(NSEvent *)theEvent
+{
+ // restore usual image
+ [self setImage:_usualImage];
+
+ [super mouseExited:theEvent];
+}
+
+- (void)resetCursorRects
+{
+ // called when the button rect has been changed
+ [self removeTrackingRect];
+ [self addTrackingRect];
+}
+
+- (void)setFrame:(NSRect)rect
+{
+ [super setFrame:rect];
+ [self resetCursorRects];
+}
+
+- (void)setBounds:(NSRect)rect
+{
+ [super setBounds:rect];
+ [self resetCursorRects];
+}
+
+#pragma mark -
+#pragma mark Archiving
+
+- (void)encodeWithCoder:(NSCoder *)aCoder
+{
+ [super encodeWithCoder:aCoder];
+ if ([aCoder allowsKeyedCoding]) {
+ [aCoder encodeObject:_rolloverImage forKey:@"rolloverImage"];
+ [aCoder encodeObject:_usualImage forKey:@"usualImage"];
+ [aCoder encodeInt64:_myTrackingRectTag forKey:@"myTrackingRectTag"];
+ }
+}
+
+- (id)initWithCoder:(NSCoder *)aDecoder
+{
+ self = [super initWithCoder:aDecoder];
+ if (self) {
+ if ([aDecoder allowsKeyedCoding]) {
+ _rolloverImage = [aDecoder decodeObjectForKey:@"rolloverImage"];
+ _usualImage = [aDecoder decodeObjectForKey:@"usualImage"];
+ _myTrackingRectTag = [aDecoder decodeInt64ForKey:@"myTrackingRectTag"];
+ }
+ }
+ return self;
+}
+
+@end
diff --git a/frontends/cocoa/PSMTabBarControl/PSMTabBarCell.h b/frontends/cocoa/PSMTabBarControl/PSMTabBarCell.h
new file mode 100644
index 000000000..f5c51b495
--- /dev/null
+++ b/frontends/cocoa/PSMTabBarControl/PSMTabBarCell.h
@@ -0,0 +1,115 @@
+//
+// PSMTabBarCell.h
+// PSMTabBarControl
+//
+// Created by John Pannell on 10/13/05.
+// Copyright 2005 Positive Spin Media. All rights reserved.
+//
+
+#import <Cocoa/Cocoa.h>
+#import "PSMTabBarControl.h"
+#import "PSMProgressIndicator.h"
+
+@interface PSMTabBarCell : NSActionCell {
+ // sizing
+ NSRect _frame;
+ NSSize _stringSize;
+ NSInteger _currentStep;
+ BOOL _isPlaceholder;
+
+ // state
+ NSInteger _tabState;
+ NSTrackingRectTag _closeButtonTrackingTag; // left side tracking, if dragging
+ NSTrackingRectTag _cellTrackingTag; // right side tracking, if dragging
+ BOOL _closeButtonOver;
+ BOOL _closeButtonPressed;
+ PSMProgressIndicator *_indicator;
+ BOOL _isInOverflowMenu;
+ BOOL _hasCloseButton;
+ BOOL _isCloseButtonSuppressed;
+ BOOL _hasIcon;
+ BOOL _hasLargeImage;
+ NSInteger _count;
+ NSColor *_countColor;
+ BOOL _isEdited;
+}
+
+// creation/destruction
+- (id)initWithControlView:(PSMTabBarControl *)controlView;
+- (id)initPlaceholderWithFrame:(NSRect)frame expanded:(BOOL)value inControlView:(PSMTabBarControl *)controlView;
+- (void)dealloc;
+
+// accessors
+- (id)controlView;
+- (void)setControlView:(id)view;
+- (NSTrackingRectTag)closeButtonTrackingTag;
+- (void)setCloseButtonTrackingTag:(NSTrackingRectTag)tag;
+- (NSTrackingRectTag)cellTrackingTag;
+- (void)setCellTrackingTag:(NSTrackingRectTag)tag;
+- (CGFloat)width;
+- (NSRect)frame;
+- (void)setFrame:(NSRect)rect;
+- (void)setStringValue:(NSString *)aString;
+- (NSSize)stringSize;
+- (NSAttributedString *)attributedStringValue;
+- (NSInteger)tabState;
+- (void)setTabState:(NSInteger)state;
+- (NSProgressIndicator *)indicator;
+- (BOOL)isInOverflowMenu;
+- (void)setIsInOverflowMenu:(BOOL)value;
+- (BOOL)closeButtonPressed;
+- (void)setCloseButtonPressed:(BOOL)value;
+- (BOOL)closeButtonOver;
+- (void)setCloseButtonOver:(BOOL)value;
+- (BOOL)hasCloseButton;
+- (void)setHasCloseButton:(BOOL)set;
+- (void)setCloseButtonSuppressed:(BOOL)suppress;
+- (BOOL)isCloseButtonSuppressed;
+- (BOOL)hasIcon;
+- (void)setHasIcon:(BOOL)value;
+- (BOOL)hasLargeImage;
+- (void)setHasLargeImage:(BOOL)value;
+- (NSInteger)count;
+- (void)setCount:(NSInteger)value;
+- (NSColor *)countColor;
+- (void)setCountColor:(NSColor *)value;
+- (BOOL)isPlaceholder;
+- (void)setIsPlaceholder:(BOOL)value;
+- (NSInteger)currentStep;
+- (void)setCurrentStep:(NSInteger)value;
+- (BOOL)isEdited;
+- (void)setIsEdited:(BOOL)value;
+
+// component attributes
+- (NSRect)indicatorRectForFrame:(NSRect)cellFrame;
+- (NSRect)closeButtonRectForFrame:(NSRect)cellFrame;
+- (CGFloat)minimumWidthOfCell;
+- (CGFloat)desiredWidthOfCell;
+
+// drawing
+- (void)drawWithFrame:(NSRect)cellFrame inView:(NSView *)controlView;
+
+// tracking the mouse
+- (void)mouseEntered:(NSEvent *)theEvent;
+- (void)mouseExited:(NSEvent *)theEvent;
+
+// drag support
+- (NSImage *)dragImage;
+
+// archiving
+- (void)encodeWithCoder:(NSCoder *)aCoder;
+- (id)initWithCoder:(NSCoder *)aDecoder;
+
+@end
+
+@interface PSMTabBarControl (CellAccessors)
+
+- (id<PSMTabStyle>)style;
+
+@end
+
+@interface NSObject (IdentifierAccesors)
+
+- (NSImage *)largeImage;
+
+@end
diff --git a/frontends/cocoa/PSMTabBarControl/PSMTabBarCell.m b/frontends/cocoa/PSMTabBarControl/PSMTabBarCell.m
new file mode 100644
index 000000000..5a8edfd77
--- /dev/null
+++ b/frontends/cocoa/PSMTabBarControl/PSMTabBarCell.m
@@ -0,0 +1,536 @@
+//
+// PSMTabBarCell.m
+// PSMTabBarControl
+//
+// Created by John Pannell on 10/13/05.
+// Copyright 2005 Positive Spin Media. All rights reserved.
+//
+
+#import "PSMTabBarCell.h"
+#import "PSMTabBarControl.h"
+#import "PSMTabStyle.h"
+#import "PSMProgressIndicator.h"
+#import "PSMTabDragAssistant.h"
+#import "PSMTabBarControl+Private.h"
+
+@implementation PSMTabBarCell
+
+#pragma mark -
+#pragma mark Creation/Destruction
+- (id)initWithControlView:(PSMTabBarControl *)controlView
+{
+ if ((self = [super init])) {
+ _controlView = controlView;
+ _closeButtonTrackingTag = 0;
+ _cellTrackingTag = 0;
+ _closeButtonOver = NO;
+ _closeButtonPressed = NO;
+ _indicator = [[PSMProgressIndicator alloc] initWithFrame:NSMakeRect(0.0, 0.0, kPSMTabBarIndicatorWidth, kPSMTabBarIndicatorWidth)];
+ [_indicator setStyle:NSProgressIndicatorSpinningStyle];
+ [_indicator setAutoresizingMask:NSViewMinYMargin];
+ _hasCloseButton = YES;
+ _isCloseButtonSuppressed = NO;
+ _count = 0;
+ _countColor = nil;
+ _isEdited = NO;
+ _isPlaceholder = NO;
+ }
+ return self;
+}
+
+- (id)initPlaceholderWithFrame:(NSRect)frame expanded:(BOOL)value inControlView:(PSMTabBarControl *)controlView
+{
+ if ((self = [super init])) {
+ _controlView = controlView;
+ _isPlaceholder = YES;
+ if (!value) {
+ if ([controlView orientation] == PSMTabBarHorizontalOrientation) {
+ frame.size.width = 0.0;
+ } else {
+ frame.size.height = 0.0;
+ }
+ }
+ [self setFrame:frame];
+ _closeButtonTrackingTag = 0;
+ _cellTrackingTag = 0;
+ _closeButtonOver = NO;
+ _closeButtonPressed = NO;
+ _indicator = nil;
+ _hasCloseButton = YES;
+ _isCloseButtonSuppressed = NO;
+ _count = 0;
+ _countColor = nil;
+ _isEdited = NO;
+
+ if (value) {
+ [self setCurrentStep:(kPSMTabDragAnimationSteps - 1)];
+ } else {
+ [self setCurrentStep:0];
+ }
+ }
+ return self;
+}
+
+- (void)dealloc
+{
+
+ [_indicator removeFromSuperviewWithoutNeedingDisplay];
+}
+
+#pragma mark -
+#pragma mark Accessors
+
+- (id)controlView
+{
+ return _controlView;
+}
+
+- (void)setControlView:(id)view
+{
+ // no retain release pattern, as this simply switches a tab to another view.
+ _controlView = view;
+}
+
+- (NSTrackingRectTag)closeButtonTrackingTag
+{
+ return _closeButtonTrackingTag;
+}
+
+- (void)setCloseButtonTrackingTag:(NSTrackingRectTag)tag
+{
+ _closeButtonTrackingTag = tag;
+}
+
+- (NSTrackingRectTag)cellTrackingTag
+{
+ return _cellTrackingTag;
+}
+
+- (void)setCellTrackingTag:(NSTrackingRectTag)tag
+{
+ _cellTrackingTag = tag;
+}
+
+- (CGFloat)width
+{
+ return _frame.size.width;
+}
+
+- (NSRect)frame
+{
+ return _frame;
+}
+
+- (void)setFrame:(NSRect)rect
+{
+ _frame = rect;
+
+ //move the status indicator along with the rest of the cell
+ if (![[self indicator] isHidden] && ![_controlView isTabBarHidden]) {
+ [[self indicator] setFrame:[self indicatorRectForFrame:rect]];
+ }
+}
+
+- (void)setStringValue:(NSString *)aString
+{
+ [super setStringValue:aString];
+ _stringSize = [[self attributedStringValue] size];
+ // need to redisplay now - binding observation was too quick.
+ [_controlView update];
+}
+
+- (NSSize)stringSize
+{
+ return _stringSize;
+}
+
+- (NSAttributedString *)attributedStringValue
+{
+ return [(id<PSMTabStyle>)[(PSMTabBarControl *)_controlView style] attributedStringValueForTabCell:self];
+}
+
+- (NSInteger)tabState
+{
+ return _tabState;
+}
+
+- (void)setTabState:(NSInteger)state
+{
+ _tabState = state;
+}
+
+- (NSProgressIndicator *)indicator
+{
+ return _indicator;
+}
+
+- (BOOL)isInOverflowMenu
+{
+ return _isInOverflowMenu;
+}
+
+- (void)setIsInOverflowMenu:(BOOL)value
+{
+ if (_isInOverflowMenu != value) {
+ _isInOverflowMenu = value;
+ if ([[[self controlView] delegate] respondsToSelector:@selector(tabView:tabViewItem:isInOverflowMenu:)]) {
+ [[[self controlView] delegate] tabView:[self controlView] tabViewItem:[self representedObject] isInOverflowMenu:_isInOverflowMenu];
+ }
+ }
+}
+
+- (BOOL)closeButtonPressed
+{
+ return _closeButtonPressed;
+}
+
+- (void)setCloseButtonPressed:(BOOL)value
+{
+ _closeButtonPressed = value;
+}
+
+- (BOOL)closeButtonOver
+{
+ return (_closeButtonOver && ([_controlView allowsBackgroundTabClosing] || ([self tabState] & PSMTab_SelectedMask) || [[NSApp currentEvent] modifierFlags] & NSEventModifierFlagCommand));
+}
+
+- (void)setCloseButtonOver:(BOOL)value
+{
+ _closeButtonOver = value;
+}
+
+- (BOOL)hasCloseButton
+{
+ return _hasCloseButton;
+}
+
+- (void)setHasCloseButton:(BOOL)set
+{
+ _hasCloseButton = set;
+}
+
+- (void)setCloseButtonSuppressed:(BOOL)suppress
+{
+ _isCloseButtonSuppressed = suppress;
+}
+
+- (BOOL)isCloseButtonSuppressed
+{
+ return _isCloseButtonSuppressed;
+}
+
+- (BOOL)hasIcon
+{
+ return _hasIcon;
+}
+
+- (void)setHasIcon:(BOOL)value
+{
+ _hasIcon = value;
+ //[_controlView update:[[self controlView] automaticallyAnimates]]; // binding notice is too fast
+}
+
+- (BOOL)hasLargeImage
+{
+ return _hasLargeImage;
+}
+
+- (void)setHasLargeImage:(BOOL)value
+{
+ _hasLargeImage = value;
+}
+
+- (NSInteger)count
+{
+ return _count;
+}
+
+- (void)setCount:(NSInteger)value
+{
+ _count = value;
+ //[_controlView update:[[self controlView] automaticallyAnimates]]; // binding notice is too fast
+}
+
+- (NSColor *)countColor
+{
+ return _countColor;
+}
+
+- (void)setCountColor:(NSColor *)color
+{
+ _countColor = color;
+}
+
+- (BOOL)isPlaceholder
+{
+ return _isPlaceholder;
+}
+
+- (void)setIsPlaceholder:(BOOL)value
+{
+ _isPlaceholder = value;
+}
+
+- (NSInteger)currentStep
+{
+ return _currentStep;
+}
+
+- (void)setCurrentStep:(NSInteger)value
+{
+ if (value < 0) {
+ value = 0;
+ }
+
+ if (value > (kPSMTabDragAnimationSteps - 1)) {
+ value = (kPSMTabDragAnimationSteps - 1);
+ }
+
+ _currentStep = value;
+}
+
+- (BOOL)isEdited
+{
+ return _isEdited;
+}
+
+- (void)setIsEdited:(BOOL)value
+{
+ _isEdited = value;
+ //[_controlView update:[[self controlView] automaticallyAnimates]]; // binding notice is too fast
+}
+
+#pragma mark -
+#pragma mark Bindings
+
+- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context
+{
+ // the progress indicator, label, icon, or count has changed - redraw the control view
+ //[_controlView update];
+ //I seem to have run into some odd issue with update not being called at the right time. This seems to avoid the problem.
+ [_controlView performSelector:@selector(update) withObject:nil afterDelay:0.0];
+}
+
+#pragma mark -
+#pragma mark Component Attributes
+
+- (NSRect)indicatorRectForFrame:(NSRect)cellFrame
+{
+ return [(id<PSMTabStyle>)[(PSMTabBarControl *)_controlView style] indicatorRectForTabCell:self];
+}
+
+- (NSRect)closeButtonRectForFrame:(NSRect)cellFrame
+{
+ return [(id<PSMTabStyle>)[(PSMTabBarControl *)_controlView style] closeButtonRectForTabCell:self withFrame:cellFrame];
+}
+
+- (CGFloat)minimumWidthOfCell
+{
+ return [(id<PSMTabStyle>)[(PSMTabBarControl *)_controlView style] minimumWidthOfTabCell:self];
+}
+
+- (CGFloat)desiredWidthOfCell
+{
+ return [(id<PSMTabStyle>)[(PSMTabBarControl *)_controlView style] desiredWidthOfTabCell:self];
+}
+
+#pragma mark -
+#pragma mark Drawing
+
+- (void)drawWithFrame:(NSRect)cellFrame inView:(NSView *)controlView
+{
+ if (_isPlaceholder) {
+ [[NSColor colorWithCalibratedWhite:0.0 alpha:0.2] set];
+ NSRectFillUsingOperation(cellFrame, NSCompositingOperationSourceAtop);
+ return;
+ }
+
+ [(id<PSMTabStyle>)[(PSMTabBarControl *)_controlView style] drawTabCell:self];
+}
+
+#pragma mark -
+#pragma mark Tracking
+
+- (void)mouseEntered:(NSEvent *)theEvent
+{
+ // check for which tag
+ if ([theEvent trackingNumber] == _closeButtonTrackingTag) {
+ _closeButtonOver = YES;
+ }
+ if ([theEvent trackingNumber] == _cellTrackingTag) {
+ [self setHighlighted:YES];
+ [_controlView setNeedsDisplay:NO];
+ }
+
+ // scrubtastic
+ if ([_controlView allowsScrubbing] && ([theEvent modifierFlags] & NSEventModifierFlagOption)) {
+ [_controlView performSelector:@selector(tabClick:) withObject:self];
+ }
+
+ // tell the control we only need to redraw the affected tab
+ [_controlView setNeedsDisplayInRect:NSInsetRect([self frame], -2, -2)];
+}
+
+- (void)mouseExited:(NSEvent *)theEvent
+{
+ // check for which tag
+ if ([theEvent trackingNumber] == _closeButtonTrackingTag) {
+ _closeButtonOver = NO;
+ }
+
+ if ([theEvent trackingNumber] == _cellTrackingTag) {
+ [self setHighlighted:NO];
+ [_controlView setNeedsDisplay:NO];
+ }
+
+ //tell the control we only need to redraw the affected tab
+ [_controlView setNeedsDisplayInRect:NSInsetRect([self frame], -2, -2)];
+}
+
+#pragma mark -
+#pragma mark Drag Support
+
+- (NSImage *)dragImage
+{
+ NSRect cellFrame = [(id<PSMTabStyle>)[(PSMTabBarControl *)_controlView style] dragRectForTabCell:self orientation:(PSMTabBarOrientation)[(PSMTabBarControl *)_controlView orientation]];
+ //NSRect cellFrame = [self frame];
+
+ [_controlView lockFocus];
+ NSBitmapImageRep *rep = [[NSBitmapImageRep alloc] initWithFocusedViewRect:cellFrame];
+ [_controlView unlockFocus];
+ NSImage *image = [[NSImage alloc] initWithSize:[rep size]];
+ [image addRepresentation:rep];
+ NSImage *returnImage = [[NSImage alloc] initWithSize:[rep size]];
+ [returnImage lockFocus];
+ [image drawAtPoint:NSMakePoint(0.0, 0.0) fromRect:NSZeroRect operation:NSCompositingOperationSourceOver fraction:1.0];
+ [returnImage unlockFocus];
+ if (![[self indicator] isHidden]) {
+ NSImage *pi = [[NSImage alloc] initByReferencingFile:[[PSMTabBarControl bundle] pathForImageResource:@"pi"]];
+ [returnImage lockFocus];
+ NSPoint indicatorPoint = NSMakePoint([self frame].size.width - MARGIN_X - kPSMTabBarIndicatorWidth, MARGIN_Y);
+ [pi drawAtPoint:indicatorPoint fromRect:NSZeroRect operation:NSCompositingOperationSourceOver fraction:1.0];
+ [returnImage unlockFocus];
+ }
+ return returnImage;
+}
+
+#pragma mark -
+#pragma mark Archiving
+
+- (void)encodeWithCoder:(NSCoder *)aCoder
+{
+ [super encodeWithCoder:aCoder];
+ if ([aCoder allowsKeyedCoding]) {
+ [aCoder encodeRect:_frame forKey:@"frame"];
+ [aCoder encodeSize:_stringSize forKey:@"stringSize"];
+ [aCoder encodeInteger:_currentStep forKey:@"currentStep"];
+ [aCoder encodeBool:_isPlaceholder forKey:@"isPlaceholder"];
+ [aCoder encodeInteger:_tabState forKey:@"tabState"];
+ [aCoder encodeInteger:_closeButtonTrackingTag forKey:@"closeButtonTrackingTag"];
+ [aCoder encodeInteger:_cellTrackingTag forKey:@"cellTrackingTag"];
+ [aCoder encodeBool:_closeButtonOver forKey:@"closeButtonOver"];
+ [aCoder encodeBool:_closeButtonPressed forKey:@"closeButtonPressed"];
+ [aCoder encodeObject:_indicator forKey:@"indicator"];
+ [aCoder encodeBool:_isInOverflowMenu forKey:@"isInOverflowMenu"];
+ [aCoder encodeBool:_hasCloseButton forKey:@"hasCloseButton"];
+ [aCoder encodeBool:_isCloseButtonSuppressed forKey:@"isCloseButtonSuppressed"];
+ [aCoder encodeBool:_hasIcon forKey:@"hasIcon"];
+ [aCoder encodeBool:_hasLargeImage forKey:@"hasLargeImage"];
+ [aCoder encodeInteger:_count forKey:@"count"];
+ [aCoder encodeBool:_isEdited forKey:@"isEdited"];
+ }
+}
+
+- (id)initWithCoder:(NSCoder *)aDecoder
+{
+ self = [super initWithCoder:aDecoder];
+ if (self) {
+ if ([aDecoder allowsKeyedCoding]) {
+ _frame = [aDecoder decodeRectForKey:@"frame"];
+ _stringSize = [aDecoder decodeSizeForKey:@"stringSize"];
+ _currentStep = [aDecoder decodeIntegerForKey:@"currentStep"];
+ _isPlaceholder = [aDecoder decodeBoolForKey:@"isPlaceholder"];
+ _tabState = [aDecoder decodeIntegerForKey:@"tabState"];
+ _closeButtonTrackingTag = [aDecoder decodeIntegerForKey:@"closeButtonTrackingTag"];
+ _cellTrackingTag = [aDecoder decodeIntegerForKey:@"cellTrackingTag"];
+ _closeButtonOver = [aDecoder decodeBoolForKey:@"closeButtonOver"];
+ _closeButtonPressed = [aDecoder decodeBoolForKey:@"closeButtonPressed"];
+ _indicator = [aDecoder decodeObjectForKey:@"indicator"];
+ _isInOverflowMenu = [aDecoder decodeBoolForKey:@"isInOverflowMenu"];
+ _hasCloseButton = [aDecoder decodeBoolForKey:@"hasCloseButton"];
+ _isCloseButtonSuppressed = [aDecoder decodeBoolForKey:@"isCloseButtonSuppressed"];
+ _hasIcon = [aDecoder decodeBoolForKey:@"hasIcon"];
+ _hasLargeImage = [aDecoder decodeBoolForKey:@"hasLargeImage"];
+ _count = [aDecoder decodeIntegerForKey:@"count"];
+ _isEdited = [aDecoder decodeBoolForKey:@"isEdited"];
+ }
+ }
+ return self;
+}
+
+#pragma mark -
+#pragma mark Accessibility
+
+- (BOOL)accessibilityIsIgnored
+{
+ return NO;
+}
+
+- (id)accessibilityAttributeValue:(NSString *)attribute
+{
+ id attributeValue = nil;
+
+ if ([attribute isEqualToString:NSAccessibilityRoleAttribute]) {
+ attributeValue = NSAccessibilityButtonRole;
+ } else if ([attribute isEqualToString:NSAccessibilityHelpAttribute]) {
+ if ([[[self controlView] delegate] respondsToSelector:@selector(accessibilityStringForTabView:objectCount:)]) {
+ attributeValue = [NSString stringWithFormat:@"%@, %lu %@", [self stringValue],
+ (unsigned long)[self count],
+ [[[self controlView] delegate] accessibilityStringForTabView:[[self controlView] tabView]
+ objectCount:[self count]]];
+ } else {
+ attributeValue = [self stringValue];
+ }
+ } else if ([attribute isEqualToString:NSAccessibilityFocusedAttribute]) {
+ attributeValue = [NSNumber numberWithBool:([self tabState] == 2)];
+ } else {
+ attributeValue = [super accessibilityAttributeValue:attribute];
+ }
+
+ return attributeValue;
+}
+
+- (NSArray *)accessibilityActionNames
+{
+ static NSArray *actions;
+
+ if (!actions) {
+ actions = [[NSArray alloc] initWithObjects:NSAccessibilityPressAction, nil];
+ }
+ return actions;
+}
+
+- (NSString *)accessibilityActionDescription:(NSString *)action
+{
+ return NSAccessibilityActionDescription(action);
+}
+
+- (void)accessibilityPerformAction:(NSString *)action
+{
+ if ([action isEqualToString:NSAccessibilityPressAction]) {
+ // this tab was selected
+ [_controlView performSelector:@selector(tabClick:) withObject:self];
+ }
+}
+
+- (id)accessibilityHitTest:(NSPoint)point
+{
+ return NSAccessibilityUnignoredAncestor(self);
+}
+
+- (id)accessibilityFocusedUIElement:(NSPoint)point
+{
+ return NSAccessibilityUnignoredAncestor(self);
+}
+
+@end
diff --git a/frontends/cocoa/PSMTabBarControl/PSMTabBarControl+Private.h b/frontends/cocoa/PSMTabBarControl/PSMTabBarControl+Private.h
new file mode 100644
index 000000000..8df3fd368
--- /dev/null
+++ b/frontends/cocoa/PSMTabBarControl/PSMTabBarControl+Private.h
@@ -0,0 +1,49 @@
+#import "PSMTabBarControl.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
diff --git a/frontends/cocoa/PSMTabBarControl/PSMTabBarControl.h b/frontends/cocoa/PSMTabBarControl/PSMTabBarControl.h
new file mode 100644
index 000000000..26f11de7a
--- /dev/null
+++ b/frontends/cocoa/PSMTabBarControl/PSMTabBarControl.h
@@ -0,0 +1,239 @@
+//
+// PSMTabBarControl.h
+// PSMTabBarControl
+//
+// Created by John Pannell on 10/13/05.
+// Copyright 2005 Positive Spin Media. All rights reserved.
+//
+
+/*
+ This view provides a control interface to manage a regular NSTabView. It looks and works like the tabbed browsing interface of many popular browsers.
+ */
+
+#import <Cocoa/Cocoa.h>
+
+#define PSMTabDragDidEndNotification @"PSMTabDragDidEndNotification"
+#define PSMTabDragDidBeginNotification @"PSMTabDragDidBeginNotification"
+
+#define kPSMTabBarControlHeight 22
+// internal cell border
+#define MARGIN_X 6
+#define MARGIN_Y 3
+// padding between objects
+#define kPSMTabBarCellPadding 4
+// fixed size objects
+#define kPSMMinimumTitleWidth 30
+#define kPSMTabBarIndicatorWidth 16.0
+#define kPSMTabBarIconWidth 16.0
+#define kPSMHideAnimationSteps 3.0
+
+// Value used in _currentStep to indicate that resizing operation is not in progress
+#define kPSMIsNotBeingResized -1
+
+// Value used in _currentStep when a resizing operation has just been started
+#define kPSMStartResizeAnimation 0
+
+@class PSMOverflowPopUpButton;
+@class PSMRolloverButton;
+@class PSMTabBarCell;
+@class PSMTabBarController;
+@protocol PSMTabStyle;
+
+typedef enum {
+ PSMTabBarHorizontalOrientation,
+ PSMTabBarVerticalOrientation
+} PSMTabBarOrientation;
+
+typedef enum {
+ PSMTabBarTearOffAlphaWindow,
+ PSMTabBarTearOffMiniwindow
+} PSMTabBarTearOffStyle;
+
+enum {
+ PSMTab_SelectedMask = 1 << 1,
+ PSMTab_LeftIsSelectedMask = 1 << 2,
+ PSMTab_RightIsSelectedMask = 1 << 3,
+ PSMTab_PositionLeftMask = 1 << 4,
+ PSMTab_PositionMiddleMask = 1 << 5,
+ PSMTab_PositionRightMask = 1 << 6,
+ PSMTab_PositionSingleMask = 1 << 7,
+};
+
+@interface PSMTabBarControl : NSControl {
+
+ // control basics
+ NSMutableArray *_cells; // the cells that draw the tabs
+ IBOutlet NSTabView *tabView; // the tab view being navigated
+ PSMOverflowPopUpButton *_overflowPopUpButton; // for too many tabs
+ PSMRolloverButton *_addTabButton;
+ PSMTabBarController *_controller;
+
+ // Spring-loading.
+ NSTimer *_springTimer;
+ NSTabViewItem *_tabViewItemWithSpring;
+
+ // drawing style
+ id<PSMTabStyle> style;
+ BOOL _canCloseOnlyTab;
+ BOOL _disableTabClose;
+ BOOL _hideForSingleTab;
+ BOOL _showAddTabButton;
+ BOOL _sizeCellsToFit;
+ BOOL _useOverflowMenu;
+ BOOL _alwaysShowActiveTab;
+ BOOL _allowsScrubbing;
+ NSInteger _resizeAreaCompensation;
+ PSMTabBarOrientation _orientation;
+ BOOL _automaticallyAnimates;
+ NSTimer *_animationTimer;
+ PSMTabBarTearOffStyle _tearOffStyle;
+
+ // behavior
+ BOOL _allowsBackgroundTabClosing;
+ BOOL _selectsTabsOnMouseDown;
+
+ // vertical tab resizing
+ BOOL _allowsResizing;
+ BOOL _resizing;
+
+ // cell width
+ NSInteger _cellMinWidth;
+ NSInteger _cellMaxWidth;
+ NSInteger _cellOptimumWidth;
+
+ // animation for hide/show
+ NSInteger _currentStep;
+ BOOL _isHidden;
+ IBOutlet id partnerView; // gets resized when hide/show
+ BOOL _awakenedFromNib;
+ NSInteger _tabBarWidth;
+ NSTimer *_showHideAnimationTimer;
+
+ // drag and drop
+ NSEvent *_lastMouseDownEvent; // keep this for dragging reference
+ BOOL _didDrag;
+ BOOL _closeClicked;
+
+ // MVC help
+ IBOutlet id delegate;
+}
+
+// control characteristics
++ (NSBundle *)bundle;
+- (CGFloat)availableCellWidth;
+- (NSRect)genericCellRect;
+
+// control configuration
+- (PSMTabBarOrientation)orientation;
+- (void)setOrientation:(PSMTabBarOrientation)value;
+- (BOOL)canCloseOnlyTab;
+- (void)setCanCloseOnlyTab:(BOOL)value;
+- (BOOL)disableTabClose;
+- (void)setDisableTabClose:(BOOL)value;
+- (id<PSMTabStyle>)style;
+- (void)setStyle:(id<PSMTabStyle>)newStyle;
+- (NSString *)styleName;
+- (void)setStyleNamed:(NSString *)name;
+- (BOOL)hideForSingleTab;
+- (void)setHideForSingleTab:(BOOL)value;
+- (BOOL)showAddTabButton;
+- (void)setShowAddTabButton:(BOOL)value;
+- (NSInteger)cellMinWidth;
+- (void)setCellMinWidth:(NSInteger)value;
+- (NSInteger)cellMaxWidth;
+- (void)setCellMaxWidth:(NSInteger)value;
+- (NSInteger)cellOptimumWidth;
+- (void)setCellOptimumWidth:(NSInteger)value;
+- (BOOL)sizeCellsToFit;
+- (void)setSizeCellsToFit:(BOOL)value;
+- (BOOL)useOverflowMenu;
+- (void)setUseOverflowMenu:(BOOL)value;
+- (BOOL)allowsBackgroundTabClosing;
+- (void)setAllowsBackgroundTabClosing:(BOOL)value;
+- (BOOL)allowsResizing;
+- (void)setAllowsResizing:(BOOL)value;
+- (BOOL)selectsTabsOnMouseDown;
+- (void)setSelectsTabsOnMouseDown:(BOOL)value;
+- (BOOL)automaticallyAnimates;
+- (void)setAutomaticallyAnimates:(BOOL)value;
+- (BOOL)alwaysShowActiveTab;
+- (void)setAlwaysShowActiveTab:(BOOL)value;
+- (BOOL)allowsScrubbing;
+- (void)setAllowsScrubbing:(BOOL)value;
+- (PSMTabBarTearOffStyle)tearOffStyle;
+- (void)setTearOffStyle:(PSMTabBarTearOffStyle)tearOffStyle;
+
+// Factory for default style
++ (Class)defaultStyleClass;
+
+// accessors
+- (NSTabView *)tabView;
+- (void)setTabView:(NSTabView *)view;
+- (id)delegate;
+- (void)setDelegate:(id)object;
+- (id)partnerView;
+- (void)setPartnerView:(id)view;
+
+// the buttons
+- (PSMRolloverButton *)addTabButton;
+- (PSMOverflowPopUpButton *)overflowPopUpButton;
+
+// tab information
+- (NSMutableArray *)representedTabViewItems;
+- (NSInteger)numberOfVisibleTabs;
+- (PSMTabBarCell *)lastVisibleTab;
+
+// special effects
+- (void)hideTabBar:(BOOL)hide animate:(BOOL)animate;
+- (BOOL)isTabBarHidden;
+- (BOOL)isAnimating;
+
+// internal bindings methods also used by the tab drag assistant
+- (void)bindPropertiesForCell:(PSMTabBarCell *)cell andTabViewItem:(NSTabViewItem *)item;
+- (void)removeTabForCell:(PSMTabBarCell *)cell;
+
+@end
+
+@interface NSObject (TabBarControlDelegateMethods)
+
+//Standard NSTabView methods
+- (BOOL)tabView:(NSTabView *)aTabView shouldCloseTabViewItem:(NSTabViewItem *)tabViewItem;
+- (void)tabView:(NSTabView *)aTabView didCloseTabViewItem:(NSTabViewItem *)tabViewItem;
+
+//"Spring-loaded" tabs methods
+- (NSArray *)allowedDraggedTypesForTabView:(NSTabView *)aTabView;
+- (void)tabView:(NSTabView *)aTabView acceptedDraggingInfo:(id<NSDraggingInfo>)draggingInfo onTabViewItem:(NSTabViewItem *)tabViewItem;
+
+//Contextual menu method
+- (NSMenu *)tabView:(NSTabView *)aTabView menuForTabViewItem:(NSTabViewItem *)tabViewItem;
+
+//Drag and drop methods
+- (BOOL)tabView:(NSTabView *)aTabView shouldDragTabViewItem:(NSTabViewItem *)tabViewItem fromTabBar:(PSMTabBarControl *)tabBarControl;
+- (BOOL)tabView:(NSTabView *)aTabView shouldDropTabViewItem:(NSTabViewItem *)tabViewItem inTabBar:(PSMTabBarControl *)tabBarControl;
+- (BOOL)tabView:(NSTabView *)aTabView shouldAllowTabViewItem:(NSTabViewItem *)tabViewItem toLeaveTabBar:(PSMTabBarControl *)tabBarControl;
+- (void)tabView:(NSTabView *)aTabView didDropTabViewItem:(NSTabViewItem *)tabViewItem inTabBar:(PSMTabBarControl *)tabBarControl;
+
+//Tear-off tabs methods
+- (NSImage *)tabView:(NSTabView *)aTabView imageForTabViewItem:(NSTabViewItem *)tabViewItem offset:(NSSize *)offset styleMask:(NSUInteger *)styleMask;
+- (PSMTabBarControl *)tabView:(NSTabView *)aTabView newTabBarForDraggedTabViewItem:(NSTabViewItem *)tabViewItem atPoint:(NSPoint)point;
+- (void)tabView:(NSTabView *)aTabView closeWindowForLastTabViewItem:(NSTabViewItem *)tabViewItem;
+
+//Overflow menu validation
+- (BOOL)tabView:(NSTabView *)aTabView validateOverflowMenuItem:(NSMenuItem *)menuItem forTabViewItem:(NSTabViewItem *)tabViewItem;
+- (void)tabView:(NSTabView *)aTabView tabViewItem:(NSTabViewItem *)tabViewItem isInOverflowMenu:(BOOL)inOverflowMenu;
+
+//tab bar hiding methods
+- (void)tabView:(NSTabView *)aTabView tabBarDidHide:(PSMTabBarControl *)tabBarControl;
+- (void)tabView:(NSTabView *)aTabView tabBarDidUnhide:(PSMTabBarControl *)tabBarControl;
+- (CGFloat)desiredWidthForVerticalTabBar:(PSMTabBarControl *)tabBarControl;
+
+//closing
+- (BOOL)tabView:(NSTabView *)aTabView disableTabCloseForTabViewItem:(NSTabViewItem *)tabViewItem;
+
+//tooltips
+- (NSString *)tabView:(NSTabView *)aTabView toolTipForTabViewItem:(NSTabViewItem *)tabViewItem;
+
+//accessibility
+- (NSString *)accessibilityStringForTabView:(NSTabView *)aTabView objectCount:(NSInteger)objectCount;
+
+@end
diff --git a/frontends/cocoa/PSMTabBarControl/PSMTabBarControl.m b/frontends/cocoa/PSMTabBarControl/PSMTabBarControl.m
new file mode 100644
index 000000000..0d6a2d7bc
--- /dev/null
+++ b/frontends/cocoa/PSMTabBarControl/PSMTabBarControl.m
@@ -0,0 +1,2035 @@
+//
+// PSMTabBarControl.m
+// PSMTabBarControl
+//
+// Created by John Pannell on 10/13/05.
+// Copyright 2005 Positive Spin Media. All rights reserved.
+//
+
+#import <objc/runtime.h>
+
+#import "PSMTabBarControl.h"
+#import "PSMTabBarCell.h"
+#import "PSMOverflowPopUpButton.h"
+#import "PSMRolloverButton.h"
+#import "PSMTabStyle.h"
+#import "PSMUnifiedTabStyle.h"
+#import "PSMTabDragAssistant.h"
+#import "PSMTabBarController.h"
+
+#import "PSMTabBarControl+Private.h"
+
+@protocol PSMTabBarItem
+@optional
+- (BOOL)isProcessing;
+
+@optional
+- (int)objectCount;
+
+@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 = [[[[self class] defaultStyleClass] 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];
+ }
+}
+
++ (Class)defaultStyleClass
+{
+ return [PSMUnifiedTabStyle class];
+}
+
+- (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];
+
+ [_showHideAnimationTimer invalidate];
+
+ //Also unwind the spring, if it's wound.
+ [_springTimer invalidate];
+
+ //unbind all the items to prevent crashing
+ //not sure if this is necessary or not
+ // http://code.google.com/p/maccode/issues/detail?id=35
+ NSEnumerator *enumerator = [[_cells copy] objectEnumerator];
+ PSMTabBarCell *nextCell;
+ while ((nextCell = [enumerator nextObject])) {
+ [self removeTabForCell:nextCell];
+ }
+
+ [self unregisterDraggedTypes];
+}
+
+- (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 = 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
+{
+ _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
+{
+ tabView = view;
+}
+
+- (id<PSMTabStyle>)style
+{
+ return style;
+}
+
+- (NSString *)styleName
+{
+ return [style name];
+}
+
+- (void)setStyle:(id<PSMTabStyle>)newStyle
+{
+ if (style != newStyle) {
+
+ // 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
+{
+
+ Class styleClass = NSClassFromString([NSString stringWithFormat:@"PSM%@TabStyle", [name capitalizedString]]);
+ if (styleClass == Nil) {
+ styleClass = object_getClass([PSMTabBarControl defaultStyleClass]);
+ }
+
+ id<PSMTabStyle> newStyle = [[styleClass alloc] init];
+ [self setStyle:newStyle];
+}
+
+- (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;
+ if ([_cells lastObject] != nil) {
+ cellRect = lastCellFrame = [[_cells lastObject] frame];
+ } else {
+ cellRect = lastCellFrame = NSZeroRect;
+ }
+
+ 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];
+ if ([_cells count] == (NSUInteger)[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 = [NSTimer scheduledTimerWithTimeInterval:(1.0 / 30.0) target:self selector:@selector(animateShowHide:) userInfo:userInfo repeats:YES];
+}
+
+- (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 = nil;
+ }
+ [[self window] display];
+}
+
+- (BOOL)isTabBarHidden
+{
+ return _isHidden;
+}
+
+- (BOOL)isAnimating
+{
+ return _animationTimer != nil;
+}
+
+- (id)partnerView
+{
+ return partnerView;
+}
+
+- (void)setPartnerView:(id)view
+{
+ 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 ((NSUInteger)[[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];
+
+ [_animationTimer invalidate];
+ _animationTimer = nil;
+
+ if (animate) {
+ NSMutableArray *targetFrames = [NSMutableArray arrayWithCapacity:[_cells count]];
+
+ for (NSUInteger 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];
+ [[NSRunLoop currentRunLoop] addTimer:_animationTimer forMode:NSEventTrackingRunLoopMode];
+ [self _animateCells:_animationTimer];
+ } else {
+ for (NSUInteger 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;
+ NSUInteger cellCount = [_cells count];
+
+ if ((cellCount > 0) && [animation isAnimating]) {
+ //compare our target position with the current position and move towards the target
+ for (NSUInteger 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 (NSUInteger 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 = nil;
+
+ for (NSUInteger 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] & NSWindowStyleMaskTexturedBackground ? 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] & NSEventModifierFlagCommand)) {
+ [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] & NSEventModifierFlagOption) != 0) {
+ //If the user is holding Option, close all other tabs
+ NSEnumerator *enumerator = [[[self cells] copy] 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
+- (NSDragOperation)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 = 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 = (__bridge_transfer NSNumber *)CFPreferencesCopyAppValue((CFStringRef) @"SpringingDelayMilliseconds", (CFStringRef) @"com.apple.finder");
+ NSTimeInterval delaySeconds = delayNumber ? [delayNumber doubleValue] / 1000.0 : 0.668;
+ _springTimer = [NSTimer scheduledTimerWithTimeInterval:delaySeconds
+ target:self
+ selector:@selector(fireSpring:)
+ userInfo:sender
+ repeats:NO];
+ }
+ return NSDragOperationCopy;
+ }
+
+ return NSDragOperationNone;
+}
+
+- (void)draggingExited:(id<NSDraggingInfo>)sender
+{
+ [_springTimer invalidate];
+ _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 = 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];
+ 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];
+ return;
+ }
+ }
+
+ [tabView removeTabViewItem:item];
+}
+
+- (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
+ NSUInteger 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
+ [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
+
+ [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] 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]];
+
+ // resize
+ [self setPostsFrameChangedNotifications:YES];
+ [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(frameDidChange:) name:NSViewFrameDidChangeNotification object:self];
+ if ([aDecoder allowsKeyedCoding]) {
+ _cells = [aDecoder decodeObjectForKey:@"PSMcells"];
+ tabView = [aDecoder decodeObjectForKey:@"PSMtabView"];
+ _overflowPopUpButton = [aDecoder decodeObjectForKey:@"PSMoverflowPopUpButton"];
+ _addTabButton = [aDecoder decodeObjectForKey:@"PSMaddTabButton"];
+ style = [aDecoder decodeObjectForKey:@"PSMstyle"];
+ _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"];
+ _awakenedFromNib = [aDecoder decodeBoolForKey:@"PSMawakenedFromNib"];
+ _lastMouseDownEvent = [aDecoder decodeObjectForKey:@"PSMlastMouseDownEvent"];
+ _useOverflowMenu = [aDecoder decodeBoolForKey:@"PSMuseOverflowMenu"];
+ _automaticallyAnimates = [aDecoder decodeBoolForKey:@"PSMautomaticallyAnimates"];
+ _alwaysShowActiveTab = [aDecoder decodeBoolForKey:@"PSMalwaysShowActiveTab"];
+ delegate = [aDecoder decodeObjectForKey:@"PSMdelegate"];
+ }
+ }
+ [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
+{
+ NSUInteger 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
diff --git a/frontends/cocoa/PSMTabBarControl/PSMTabBarController.h b/frontends/cocoa/PSMTabBarControl/PSMTabBarController.h
new file mode 100644
index 000000000..80c94df4f
--- /dev/null
+++ b/frontends/cocoa/PSMTabBarControl/PSMTabBarController.h
@@ -0,0 +1,38 @@
+//
+// PSMTabBarController.h
+// PSMTabBarControl
+//
+// Created by Kent Sutherland on 11/24/06.
+// Copyright 2006 Kent Sutherland. All rights reserved.
+//
+
+#import <Cocoa/Cocoa.h>
+
+@class PSMTabBarControl, PSMTabBarCell;
+
+@interface PSMTabBarController : NSObject
+#if __MAC_OS_X_VERSION_MIN_REQUIRED >= 1060
+ <NSMenuDelegate>
+#endif
+{
+ PSMTabBarControl *_control;
+ NSMutableArray *_cellTrackingRects;
+ NSMutableArray *_closeButtonTrackingRects;
+ NSMutableArray *_cellFrames;
+ NSRect _addButtonRect;
+ NSMenu *_overflowMenu;
+}
+
+- (id)initWithTabBarControl:(PSMTabBarControl *)control;
+
+- (NSRect)addButtonRect;
+- (NSMenu *)overflowMenu;
+- (NSRect)cellTrackingRectAtIndex:(NSUInteger)index;
+- (NSRect)closeButtonTrackingRectAtIndex:(NSUInteger)index;
+- (NSRect)cellFrameAtIndex:(NSUInteger)index;
+
+- (void)setSelectedCell:(PSMTabBarCell *)cell;
+
+- (void)layoutCells;
+
+@end
diff --git a/frontends/cocoa/PSMTabBarControl/PSMTabBarController.m b/frontends/cocoa/PSMTabBarControl/PSMTabBarController.m
new file mode 100644
index 000000000..232bb4948
--- /dev/null
+++ b/frontends/cocoa/PSMTabBarControl/PSMTabBarController.m
@@ -0,0 +1,648 @@
+//
+// PSMTabBarController.m
+// PSMTabBarControl
+//
+// Created by Kent Sutherland on 11/24/06.
+// Copyright 2006 Kent Sutherland. All rights reserved.
+//
+
+#import "PSMTabBarController.h"
+#import "PSMTabBarControl.h"
+#import "PSMTabBarCell.h"
+#import "PSMTabStyle.h"
+#import "NSString_AITruncation.h"
+#import "PSMRolloverButton.h"
+
+#define MAX_OVERFLOW_MENUITEM_TITLE_LENGTH 60
+
+@interface PSMTabBarController (Private)
+- (NSArray *)_generateWidthsFromCells:(NSArray *)cells;
+- (void)_setupCells:(NSArray *)cells withWidths:(NSArray *)widths;
+@end
+
+@implementation PSMTabBarController
+
+/*!
+ @method initWithTabBarControl:
+ @abstract Creates a new PSMTabBarController instance.
+ @discussion Creates a new PSMTabBarController for controlling a PSMTabBarControl. Should only be called by
+ PSMTabBarControl.
+ @param A PSMTabBarControl.
+ @returns A newly created PSMTabBarController instance.
+ */
+
+- (id)initWithTabBarControl:(PSMTabBarControl *)control
+{
+ if ((self = [super init])) {
+ _control = control;
+ _cellTrackingRects = [[NSMutableArray alloc] init];
+ _closeButtonTrackingRects = [[NSMutableArray alloc] init];
+ _cellFrames = [[NSMutableArray alloc] init];
+ _addButtonRect = NSZeroRect;
+ }
+ return self;
+}
+
+/*!
+ @method addButtonRect
+ @abstract Returns the position for the add tab button.
+ @discussion Returns the position for the add tab button.
+ @returns The rect for the add button rect.
+ */
+
+- (NSRect)addButtonRect
+{
+ return _addButtonRect;
+}
+
+/*!
+ @method overflowMenu
+ @abstract Returns current overflow menu or nil if there is none.
+ @discussion Returns current overflow menu or nil if there is none.
+ @returns The current overflow menu.
+ */
+
+- (NSMenu *)overflowMenu
+{
+ return _overflowMenu;
+}
+
+/*!
+ @method cellTrackingRectAtIndex:
+ @abstract Returns the rect for the tracking rect at the requested index.
+ @discussion Returns the rect for the tracking rect at the requested index.
+ @param Index of a cell.
+ @returns The tracking rect of the cell at the requested index.
+ */
+
+- (NSRect)cellTrackingRectAtIndex:(NSUInteger)index
+{
+ NSRect rect;
+ if (index < [_cellTrackingRects count]) {
+ rect = [[_cellTrackingRects objectAtIndex:index] rectValue];
+ } else {
+ NSLog(@"cellTrackingRectAtIndex: Invalid index (%ld)", (long)index);
+ rect = NSZeroRect;
+ }
+ return rect;
+}
+
+/*!
+ @method closeButtonTrackingRectAtIndex:
+ @abstract Returns the tracking rect for the close button at the requested index.
+ @discussion Returns the tracking rect for the close button at the requested index.
+ @param Index of a cell.
+ @returns The close button tracking rect of the cell at the requested index.
+ */
+
+- (NSRect)closeButtonTrackingRectAtIndex:(NSUInteger)index
+{
+ NSRect rect;
+ if (index < [_closeButtonTrackingRects count]) {
+ rect = [[_closeButtonTrackingRects objectAtIndex:index] rectValue];
+ } else {
+ NSLog(@"closeButtonTrackingRectAtIndex: Invalid index (%ld)", (long)index);
+ rect = NSZeroRect;
+ }
+ return rect;
+}
+
+/*!
+ @method cellFrameAtIndex:
+ @abstract Returns the frame for the cell at the requested index.
+ @discussion Returns the frame for the cell at the requested index.
+ @param Index of a cell.
+ @returns The frame of the cell at the requested index.
+ */
+
+- (NSRect)cellFrameAtIndex:(NSUInteger)index
+{
+ NSRect rect;
+
+ if (index < [_cellFrames count]) {
+ rect = [[_cellFrames objectAtIndex:index] rectValue];
+ } else {
+ NSLog(@"cellFrameAtIndex: Invalid index (%ld)", (long)index);
+ rect = NSZeroRect;
+ }
+ return rect;
+}
+
+/*!
+ @method setSelectedCell:
+ @abstract Changes the cell states so the given cell is the currently selected cell.
+ @discussion Makes the given cell the active cell and properly recalculates the tab states for surrounding cells.
+ @param An instance of PSMTabBarCell to make active.
+ */
+
+- (void)setSelectedCell:(PSMTabBarCell *)cell
+{
+ NSArray *cells = [_control cells];
+ NSEnumerator *enumerator = [cells objectEnumerator];
+ PSMTabBarCell *lastCell = nil, *nextCell;
+
+ //deselect the previously selected tab
+ while ((nextCell = [enumerator nextObject]) && ([nextCell state] == NSOffState)) {
+ lastCell = nextCell;
+ }
+
+ [nextCell setState:NSOffState];
+ [nextCell setTabState:PSMTab_PositionMiddleMask];
+
+ if (lastCell && lastCell != [_control lastVisibleTab]) {
+ [lastCell setTabState:~[lastCell tabState] & PSMTab_RightIsSelectedMask];
+ }
+
+ if ((nextCell = [enumerator nextObject])) {
+ [nextCell setTabState:~[lastCell tabState] & PSMTab_LeftIsSelectedMask];
+ }
+
+ [cell setState:NSOnState];
+ [cell setTabState:PSMTab_SelectedMask];
+
+ if (![cell isInOverflowMenu]) {
+ NSUInteger cellIndex = [cells indexOfObject:cell];
+
+ if (cellIndex > 0) {
+ nextCell = [cells objectAtIndex:cellIndex - 1];
+ [nextCell setTabState:[nextCell tabState] | PSMTab_RightIsSelectedMask];
+ }
+
+ if (cellIndex < [cells count] - 1) {
+ nextCell = [cells objectAtIndex:cellIndex + 1];
+ [nextCell setTabState:[nextCell tabState] | PSMTab_LeftIsSelectedMask];
+ }
+ }
+}
+
+/*!
+ @method layoutCells
+ @abstract Recalculates cell positions and states.
+ @discussion This method calculates the proper frame, tabState and overflow menu status for all cells in the
+ tab bar control.
+ */
+
+- (void)layoutCells
+{
+ NSArray *cells = [_control cells];
+ NSInteger cellCount = [cells count];
+
+ // make sure all of our tabs are accounted for before updating
+ if ([[_control tabView] numberOfTabViewItems] != cellCount) {
+ return;
+ }
+
+ [_cellTrackingRects removeAllObjects];
+ [_closeButtonTrackingRects removeAllObjects];
+ [_cellFrames removeAllObjects];
+
+ NSArray *cellWidths = [self _generateWidthsFromCells:cells];
+ [self _setupCells:cells withWidths:cellWidths];
+
+ //set up the rect from the add tab button
+ _addButtonRect = [_control genericCellRect];
+ _addButtonRect.size = [[_control addTabButton] frame].size;
+ if ([_control orientation] == PSMTabBarHorizontalOrientation) {
+ _addButtonRect.origin.y = MARGIN_Y;
+ _addButtonRect.origin.x += [[cellWidths valueForKeyPath:@"@sum.floatValue"] doubleValue] + 2;
+ } else {
+ _addButtonRect.origin.x = 0;
+ _addButtonRect.origin.y = [[cellWidths lastObject] doubleValue];
+ }
+}
+
+/*!
+ * @method _shrinkWidths:towardMinimum:withAvailableWidth:
+ * @abstract Decreases widths in an array toward a minimum until they fit within availableWidth, if possible
+ * @param An array of NSNumbers
+ * @param The target minimum
+ * @param The maximum available width
+ * @returns The amount by which the total array width was shrunk
+ */
+- (NSInteger)_shrinkWidths:(NSMutableArray *)newWidths towardMinimum:(NSInteger)minimum withAvailableWidth:(CGFloat)availableWidth
+{
+ BOOL changed = NO;
+ NSInteger count = [newWidths count];
+ NSInteger totalWidths = [[newWidths valueForKeyPath:@"@sum.intValue"] integerValue];
+ NSInteger originalTotalWidths = totalWidths;
+
+ do {
+ changed = NO;
+
+ for (NSInteger q = (count - 1); q >= 0; q--) {
+ CGFloat cellWidth = [[newWidths objectAtIndex:q] doubleValue];
+ if (cellWidth - 1 >= minimum) {
+ cellWidth--;
+ totalWidths--;
+
+ [newWidths replaceObjectAtIndex:q
+ withObject:[NSNumber numberWithDouble:cellWidth]];
+
+ changed = YES;
+ }
+ }
+ } while (changed && (totalWidths > availableWidth));
+
+ return (originalTotalWidths - totalWidths);
+}
+
+/*!
+ * @function potentialMinimumForArray()
+ * @abstract Calculate the minimum total for a given array of widths
+ * @discussion The array is summed using, for each item, the minimum between the current value and the passed minimum value.
+ * This is useful for getting a sum if the array has size-to-fit widths which will be allowed to be less than the
+ * specified minimum.
+ * @param An array of widths
+ * @param The minimum
+ * @returns The smallest possible sum for the array
+ */
+static NSInteger potentialMinimumForArray(NSArray *array, NSInteger minimum)
+{
+ NSInteger runningTotal = 0;
+ NSInteger count = [array count];
+
+ for (NSInteger i = 0; i < count; i++) {
+ NSInteger currentValue = [[array objectAtIndex:i] integerValue];
+ runningTotal += MIN(currentValue, minimum);
+ }
+
+ return runningTotal;
+}
+
+/*!
+ @method _generateWidthsFromCells:
+ @abstract Calculates the width of cells that would be visible.
+ @discussion Calculates the width of cells in the tab bar and returns an array of widths for the cells that would be
+ visible. Uses large blocks of code that were previously in PSMTabBarControl's update method.
+ @param An array of PSMTabBarCells.
+ @returns An array of numbers representing the widths of cells that would be visible.
+ */
+
+- (NSArray *)_generateWidthsFromCells:(NSArray *)cells
+{
+ NSInteger cellCount = [cells count], i, numberOfVisibleCells = ([_control orientation] == PSMTabBarHorizontalOrientation) ? 1 : 0;
+ NSMutableArray *newWidths = [NSMutableArray arrayWithCapacity:cellCount];
+ id<PSMTabStyle> style = [_control style];
+ CGFloat availableWidth = [_control availableCellWidth], currentOrigin = 0, totalOccupiedWidth = 0.0, width;
+ NSRect cellRect = [_control genericCellRect], controlRect = [_control frame];
+ PSMTabBarCell *currentCell;
+
+ if ([_control orientation] == PSMTabBarVerticalOrientation) {
+ currentOrigin = [style topMarginForTabBarControl];
+ }
+
+ //Don't let cells overlap the add tab button if it is visible
+ if ([_control showAddTabButton]) {
+ availableWidth -= [self addButtonRect].size.width;
+ }
+
+ for (i = 0; i < cellCount; i++) {
+ currentCell = [cells objectAtIndex:i];
+
+ // supress close button?
+ [currentCell setCloseButtonSuppressed:((cellCount == 1 && [_control canCloseOnlyTab] == NO) ||
+ [_control disableTabClose] || ([[_control delegate] respondsToSelector:@selector(tabView:disableTabCloseForTabViewItem:)] && [[_control delegate] tabView:[_control tabView]
+ disableTabCloseForTabViewItem:[currentCell representedObject]]))];
+
+ if ([_control orientation] == PSMTabBarHorizontalOrientation) {
+ // Determine cell width
+ if ([_control sizeCellsToFit]) {
+ width = [currentCell desiredWidthOfCell];
+ if (width > [_control cellMaxWidth]) {
+ width = [_control cellMaxWidth];
+ }
+ } else {
+ width = [_control cellOptimumWidth];
+ }
+
+ width = ceil(width);
+
+ //check to see if there is not enough space to place all tabs as preferred
+ if (totalOccupiedWidth + width >= availableWidth) {
+ //There's not enough space to add currentCell at its preferred width!
+
+ //If we're not going to use the overflow menu, cram all the tab cells into the bar regardless of minimum width
+ if (![_control useOverflowMenu]) {
+ NSInteger j, averageWidth = (availableWidth / cellCount);
+
+ numberOfVisibleCells = cellCount;
+ [newWidths removeAllObjects];
+
+ for (j = 0; j < cellCount; j++) {
+ CGFloat desiredWidth = [[cells objectAtIndex:j] desiredWidthOfCell];
+ [newWidths addObject:[NSNumber numberWithDouble:(desiredWidth < averageWidth && [_control sizeCellsToFit]) ? desiredWidth : averageWidth]];
+ }
+
+ totalOccupiedWidth = [[newWidths valueForKeyPath:@"@sum.intValue"] integerValue];
+ break;
+ }
+
+ //We'll be using the overflow menu if needed.
+ numberOfVisibleCells = i;
+ if ([_control sizeCellsToFit]) {
+ BOOL remainingCellsMustGoToOverflow = NO;
+
+ totalOccupiedWidth = [[newWidths valueForKeyPath:@"@sum.intValue"] integerValue];
+
+ /* Can I squeeze it in without violating min cell width? This is the width we would take up
+ * if every cell so far were at the control minimum size (or their current size if that is less than the control minimum).
+ */
+ if ((potentialMinimumForArray(newWidths, [_control cellMinWidth]) + MIN(width, [_control cellMinWidth])) <= availableWidth) {
+ /* It's definitely possible for cells so far to be visible.
+ * Shrink other cells to allow this one to fit
+ */
+ NSInteger cellMinWidth = [_control cellMinWidth];
+
+ /* Start off adding it to the array; we know that it will eventually fit because
+ * (the potential minimum <= availableWidth)
+ *
+ * This allows average and minimum aggregates on the NSArray to work.
+ */
+ [newWidths addObject:[NSNumber numberWithDouble:width]];
+ numberOfVisibleCells++;
+
+ totalOccupiedWidth += width;
+
+ //First, try to shrink tabs toward the average. Tabs smaller than average won't change
+ totalOccupiedWidth -= [self _shrinkWidths:newWidths
+ towardMinimum:[[newWidths valueForKeyPath:@"@avg.intValue"] integerValue]
+ withAvailableWidth:availableWidth];
+
+ if (totalOccupiedWidth > availableWidth) {
+ //Next, shrink tabs toward the smallest of the existing tabs. The smallest tab won't change.
+ NSInteger smallestTabWidth = [[newWidths valueForKeyPath:@"@min.intValue"] integerValue];
+ if (smallestTabWidth > cellMinWidth) {
+ totalOccupiedWidth -= [self _shrinkWidths:newWidths
+ towardMinimum:smallestTabWidth
+ withAvailableWidth:availableWidth];
+ }
+ }
+
+ if (totalOccupiedWidth > availableWidth) {
+ //Finally, shrink tabs toward the imposed minimum size. All tabs larger than the minimum wll change.
+ totalOccupiedWidth -= [self _shrinkWidths:newWidths
+ towardMinimum:cellMinWidth
+ withAvailableWidth:availableWidth];
+ }
+
+ if (totalOccupiedWidth > availableWidth) {
+ NSLog(@"**** -[PSMTabBarController generateWidthsFromCells:] This is a failure (available %f, total %f, width is %f)",
+ availableWidth, totalOccupiedWidth, width);
+ remainingCellsMustGoToOverflow = YES;
+ }
+
+ if (totalOccupiedWidth < availableWidth) {
+ /* We're not using all available space not but exceeded available width before;
+ * stretch all cells to fully fit the bar
+ */
+ NSInteger leftoverWidth = availableWidth - totalOccupiedWidth;
+ if (leftoverWidth > 0) {
+ NSInteger q;
+ for (q = numberOfVisibleCells - 1; q >= 0; q--) {
+ NSInteger desiredAddition = (NSInteger)leftoverWidth / (q + 1);
+ NSInteger newCellWidth = (NSInteger)[[newWidths objectAtIndex:q] doubleValue] + desiredAddition;
+ [newWidths replaceObjectAtIndex:q withObject:[NSNumber numberWithDouble:newCellWidth]];
+ leftoverWidth -= desiredAddition;
+ totalOccupiedWidth += desiredAddition;
+ }
+ }
+ }
+ } else {
+ // stretch - distribute leftover room among cells, since we can't add this cell
+ NSInteger leftoverWidth = availableWidth - totalOccupiedWidth;
+ NSInteger q;
+ for (q = i - 1; q >= 0; q--) {
+ NSInteger desiredAddition = (NSInteger)leftoverWidth / (q + 1);
+ NSInteger newCellWidth = (NSInteger)[[newWidths objectAtIndex:q] doubleValue] + desiredAddition;
+ [newWidths replaceObjectAtIndex:q withObject:[NSNumber numberWithDouble:newCellWidth]];
+ leftoverWidth -= desiredAddition;
+ }
+
+ remainingCellsMustGoToOverflow = YES;
+ }
+
+ // done assigning widths; remaining cells go in overflow menu
+ if (remainingCellsMustGoToOverflow) {
+ break;
+ }
+ } else {
+ //We're not using size-to-fit
+ NSInteger revisedWidth = availableWidth / (i + 1);
+ if (revisedWidth >= [_control cellMinWidth]) {
+ NSUInteger q;
+ totalOccupiedWidth = 0;
+
+ for (q = 0; q < [newWidths count]; q++) {
+ [newWidths replaceObjectAtIndex:q withObject:[NSNumber numberWithDouble:revisedWidth]];
+ totalOccupiedWidth += revisedWidth;
+ }
+ // just squeezed this one in...
+ [newWidths addObject:[NSNumber numberWithDouble:revisedWidth]];
+ totalOccupiedWidth += revisedWidth;
+ numberOfVisibleCells++;
+ } else {
+ // couldn't fit that last one...
+ break;
+ }
+ }
+ } else {
+ //(totalOccupiedWidth < availableWidth)
+ numberOfVisibleCells = cellCount;
+ [newWidths addObject:[NSNumber numberWithDouble:width]];
+ totalOccupiedWidth += width;
+ }
+ } else {
+ //lay out vertical tabs
+ if (currentOrigin + cellRect.size.height <= controlRect.size.height) {
+ [newWidths addObject:[NSNumber numberWithDouble:currentOrigin]];
+ numberOfVisibleCells++;
+ currentOrigin += cellRect.size.height;
+ } else {
+ //out of room, the remaining tabs go into overflow
+ if ([newWidths count] > 0 && controlRect.size.height - currentOrigin < 17) {
+ [newWidths removeLastObject];
+ numberOfVisibleCells--;
+ }
+ break;
+ }
+ }
+ }
+
+ //make sure there are at least two items in the horizontal tab bar
+ if ([_control orientation] == PSMTabBarHorizontalOrientation) {
+ if (numberOfVisibleCells<2 && [cells count]> 1) {
+ PSMTabBarCell *cell1 = [cells objectAtIndex:0], *cell2 = [cells objectAtIndex:1];
+ NSNumber *cellWidth;
+
+ [newWidths removeAllObjects];
+ totalOccupiedWidth = 0;
+
+ cellWidth = [NSNumber numberWithDouble:[cell1 desiredWidthOfCell] < availableWidth * 0.5f ? [cell1 desiredWidthOfCell] : availableWidth * 0.5f];
+ [newWidths addObject:cellWidth];
+ totalOccupiedWidth += [cellWidth doubleValue];
+
+ cellWidth = [NSNumber numberWithDouble:[cell2 desiredWidthOfCell] < (availableWidth - totalOccupiedWidth) ? [cell2 desiredWidthOfCell] : (availableWidth - totalOccupiedWidth)];
+ [newWidths addObject:cellWidth];
+ totalOccupiedWidth += [cellWidth doubleValue];
+
+ if (totalOccupiedWidth < availableWidth) {
+ [newWidths replaceObjectAtIndex:0 withObject:[NSNumber numberWithDouble:availableWidth - [cellWidth doubleValue]]];
+ }
+
+ numberOfVisibleCells = 2;
+ }
+ }
+
+ return newWidths;
+}
+
+/*!
+ @method _setupCells:withWidths
+ @abstract Creates tracking rect arrays and sets the frames of the visible cells.
+ @discussion Creates tracking rect arrays and sets the cells given in the widths array.
+ */
+
+- (void)_setupCells:(NSArray *)cells withWidths:(NSArray *)widths
+{
+ NSUInteger i, tabState, cellCount = [cells count];
+ NSRect cellRect = [_control genericCellRect];
+ PSMTabBarCell *cell;
+ NSTabViewItem *selectedTabViewItem = [[_control tabView] selectedTabViewItem];
+ NSMenuItem *menuItem;
+
+ _overflowMenu = nil;
+
+ for (i = 0; i < cellCount; i++) {
+ cell = [cells objectAtIndex:i];
+
+ if (i < [widths count]) {
+ tabState = 0;
+
+ // set cell frame
+ if ([_control orientation] == PSMTabBarHorizontalOrientation) {
+ cellRect.size.width = [[widths objectAtIndex:i] doubleValue];
+ } else {
+ cellRect.size.width = [_control frame].size.width;
+ cellRect.origin.y = [[widths objectAtIndex:i] doubleValue];
+ cellRect.origin.x = 0;
+ }
+
+ [_cellFrames addObject:[NSValue valueWithRect:cellRect]];
+
+ //add tracking rects to arrays
+ [_closeButtonTrackingRects addObject:[NSValue valueWithRect:[cell closeButtonRectForFrame:cellRect]]];
+ [_cellTrackingRects addObject:[NSValue valueWithRect:cellRect]];
+
+ if ([[cell representedObject] isEqualTo:selectedTabViewItem]) {
+ [cell setState:NSOnState];
+ tabState |= PSMTab_SelectedMask;
+ // previous cell
+ if (i > 0) {
+ [[cells objectAtIndex:i - 1] setTabState:([(PSMTabBarCell *)[cells objectAtIndex:i - 1] tabState] | PSMTab_RightIsSelectedMask)];
+ }
+ // next cell - see below
+ } else {
+ [cell setState:NSOffState];
+ // see if prev cell was selected
+ if ((i > 0) && ([[cells objectAtIndex:i - 1] state] == NSOnState)) {
+ tabState |= PSMTab_LeftIsSelectedMask;
+ }
+ }
+
+ // more tab states
+ if ([widths count] == 1) {
+ tabState |= PSMTab_PositionLeftMask | PSMTab_PositionRightMask | PSMTab_PositionSingleMask;
+ } else if (i == 0) {
+ tabState |= PSMTab_PositionLeftMask;
+ } else if (i == [widths count] - 1) {
+ tabState |= PSMTab_PositionRightMask;
+ }
+
+ [cell setTabState:tabState];
+ [cell setIsInOverflowMenu:NO];
+
+ // indicator
+ if (![[cell indicator] isHidden] && ![_control isTabBarHidden]) {
+ if (![[_control subviews] containsObject:[cell indicator]]) {
+ [_control addSubview:[cell indicator]];
+ [[cell indicator] startAnimation:self];
+ }
+ }
+
+ // next...
+ cellRect.origin.x += [[widths objectAtIndex:i] doubleValue];
+ } else {
+ [cell setState:NSOffState];
+ [cell setIsInOverflowMenu:YES];
+ [[cell indicator] removeFromSuperview];
+
+ //position the cell well offscreen
+ if ([_control orientation] == PSMTabBarHorizontalOrientation) {
+ cellRect.origin.x += [[_control style] rightMarginForTabBarControl] + 20;
+ } else {
+ cellRect.origin.y = [_control frame].size.height + 2;
+ }
+
+ [_cellFrames addObject:[NSValue valueWithRect:cellRect]];
+
+ if (_overflowMenu == nil) {
+ _overflowMenu = [[NSMenu alloc] init];
+ [_overflowMenu insertItemWithTitle:@"" action:nil keyEquivalent:@"" atIndex:0]; // Because the overflowPupUpButton is a pull down menu
+ [_overflowMenu setDelegate:self];
+ }
+
+ // Each item's title is limited to 60 characters. If more than 60 characters, use an ellipsis to indicate that more exists.
+ menuItem = [_overflowMenu addItemWithTitle:[[[cell attributedStringValue] string] stringWithEllipsisByTruncatingToLength:MAX_OVERFLOW_MENUITEM_TITLE_LENGTH]
+ action:@selector(overflowMenuAction:)
+ keyEquivalent:@""];
+ [menuItem setTarget:_control];
+ [menuItem setRepresentedObject:[cell representedObject]];
+
+ if ([cell count] > 0) {
+ [menuItem setTitle:[[menuItem title] stringByAppendingFormat:@" (%lu)", (unsigned long)[cell count]]];
+ }
+ }
+ }
+}
+
+- (BOOL)menu:(NSMenu *)menu updateItem:(NSMenuItem *)menuItem atIndex:(NSInteger)index shouldCancel:(BOOL)shouldCancel
+{
+ if (menu == _overflowMenu) {
+ if ([[[menuItem representedObject] identifier] respondsToSelector:@selector(icon)]) {
+ [menuItem setImage:[[[menuItem representedObject] identifier] valueForKey:@"icon"]];
+ }
+ }
+
+ return TRUE;
+}
+
+- (NSInteger)numberOfItemsInMenu:(NSMenu *)menu
+{
+ if (menu == _overflowMenu) {
+ return [_overflowMenu numberOfItems];
+ } else {
+ NSLog(@"Warning: Unexpected menu delegate call for menu %@", menu);
+ return 0;
+ }
+}
+
+@end
+
+/*
+ PSMTabBarController will store what the current tab frame state should be like based off the last layout. PSMTabBarControl
+ has to handle fetching the new frame and then changing the tab cell frame.
+ Tab states will probably be changed immediately.
+
+ Tabs that aren't going to be visible need to have their frame set offscreen. Treat them as if they were visible.
+
+ The overflow menu is rebuilt and stored by the controller.
+
+ Arrays of tracking rects will be created here, but not applied.
+ Tracking rects are removed and added by PSMTabBarControl at the end of an animate/display cycle.
+
+ The add tab button frame is handled by this controller. Visibility and location are set by the control.
+
+ isInOverflowMenu should probably be removed in favor of a call that returns yes/no to if a cell is in overflow. (Not yet implemented)
+
+ Still need to rewrite most of the code in PSMTabDragAssistant.
+ */
diff --git a/frontends/cocoa/PSMTabBarControl/PSMTabDragAssistant.h b/frontends/cocoa/PSMTabBarControl/PSMTabDragAssistant.h
new file mode 100644
index 000000000..a3a80982a
--- /dev/null
+++ b/frontends/cocoa/PSMTabBarControl/PSMTabDragAssistant.h
@@ -0,0 +1,101 @@
+//
+// PSMTabDragAssistant.h
+// PSMTabBarControl
+//
+// Created by John Pannell on 4/10/06.
+// Copyright 2006 Positive Spin Media. All rights reserved.
+//
+
+/*
+ This class is a sigleton that manages the details of a tab drag and drop. The details were beginning to overwhelm me when keeping all of this in the control and cells :-)
+ */
+
+#import <Cocoa/Cocoa.h>
+#import "PSMTabBarControl.h"
+
+#define kPSMTabDragAnimationSteps 8
+
+@class PSMTabBarCell, PSMTabDragWindowController;
+
+@interface PSMTabDragAssistant : NSObject {
+ PSMTabBarControl *_sourceTabBar;
+ PSMTabBarControl *_destinationTabBar;
+ NSMutableSet *_participatingTabBars;
+ PSMTabBarCell *_draggedCell;
+ NSUInteger _draggedCellIndex; // for snap back
+ BOOL _isDragging;
+
+ // Support for dragging into new windows
+ PSMTabDragWindowController *_draggedTab;
+ PSMTabDragWindowController *_draggedView;
+ NSSize _dragWindowOffset;
+ NSTimer *_fadeTimer;
+ BOOL _centersDragWindows;
+ PSMTabBarTearOffStyle _currentTearOffStyle;
+
+ // Animation
+ NSTimer *_animationTimer;
+ NSMutableArray *_sineCurveWidths;
+ NSPoint _currentMouseLoc;
+ PSMTabBarCell *_targetCell;
+}
+
+// Creation/destruction
++ (PSMTabDragAssistant *)sharedDragAssistant;
+
+// Accessors
+- (PSMTabBarControl *)sourceTabBar;
+- (void)setSourceTabBar:(PSMTabBarControl *)tabBar;
+- (PSMTabBarControl *)destinationTabBar;
+- (void)setDestinationTabBar:(PSMTabBarControl *)tabBar;
+- (PSMTabBarCell *)draggedCell;
+- (void)setDraggedCell:(PSMTabBarCell *)cell;
+- (NSInteger)draggedCellIndex;
+- (void)setDraggedCellIndex:(NSInteger)value;
+- (BOOL)isDragging;
+- (void)setIsDragging:(BOOL)value;
+- (NSPoint)currentMouseLoc;
+- (void)setCurrentMouseLoc:(NSPoint)point;
+- (PSMTabBarCell *)targetCell;
+- (void)setTargetCell:(PSMTabBarCell *)cell;
+
+// Functionality
+- (void)startDraggingCell:(PSMTabBarCell *)cell fromTabBar:(PSMTabBarControl *)control withMouseDownEvent:(NSEvent *)event;
+- (void)draggingEnteredTabBar:(PSMTabBarControl *)control atPoint:(NSPoint)mouseLoc;
+- (void)draggingUpdatedInTabBar:(PSMTabBarControl *)control atPoint:(NSPoint)mouseLoc;
+- (void)draggingExitedTabBar:(PSMTabBarControl *)control;
+- (void)performDragOperation;
+- (void)draggedImageEndedAt:(NSPoint)aPoint operation:(NSDragOperation)operation;
+- (void)finishDrag;
+
+- (void)draggingBeganAt:(NSPoint)aPoint;
+- (void)draggingMovedTo:(NSPoint)aPoint;
+
+// Animation
+- (void)animateDrag:(NSTimer *)timer;
+- (void)calculateDragAnimationForTabBar:(PSMTabBarControl *)control;
+
+// Placeholder
+- (void)distributePlaceholdersInTabBar:(PSMTabBarControl *)control withDraggedCell:(PSMTabBarCell *)cell;
+- (void)distributePlaceholdersInTabBar:(PSMTabBarControl *)control;
+- (void)removeAllPlaceholdersFromTabBar:(PSMTabBarControl *)control;
+
+@end
+
+@interface PSMTabBarControl (DragAccessors)
+
+- (id<PSMTabStyle>)style;
+- (NSMutableArray *)cells;
+- (void)setControlView:(id)view;
+- (id)cellForPoint:(NSPoint)point cellFrame:(NSRectPointer)outFrame;
+- (PSMTabBarCell *)lastVisibleTab;
+- (NSInteger)numberOfVisibleTabs;
+
+@end
+
+void CGContextCopyWindowCaptureContentsToRect(void *grafport, CGRect rect, NSInteger cid, NSInteger wid, NSInteger zero);
+OSStatus CGSSetWindowTransform(NSInteger cid, NSInteger wid, CGAffineTransform transform);
+
+@interface NSApplication (CoreGraphicsUndocumented)
+- (NSInteger)contextID;
+@end
diff --git a/frontends/cocoa/PSMTabBarControl/PSMTabDragAssistant.m b/frontends/cocoa/PSMTabBarControl/PSMTabDragAssistant.m
new file mode 100644
index 000000000..0027f8736
--- /dev/null
+++ b/frontends/cocoa/PSMTabBarControl/PSMTabDragAssistant.m
@@ -0,0 +1,850 @@
+//
+// PSMTabDragAssistant.m
+// PSMTabBarControl
+//
+// Created by John Pannell on 4/10/06.
+// Copyright 2006 Positive Spin Media. All rights reserved.
+//
+
+#import "PSMTabDragAssistant.h"
+#import "PSMTabBarCell.h"
+#import "PSMTabStyle.h"
+#import "PSMTabDragWindowController.h"
+#import "PSMOverflowPopUpButton.h"
+#import "PSMRolloverButton.h"
+
+#define PI 3.1417
+
+@interface PSMTabBarControl (Private)
+- (void)update:(BOOL)animate;
+@end
+
+@interface PSMTabDragAssistant (Private)
+- (NSImage *)_imageForViewOfCell:(PSMTabBarCell *)cell styleMask:(NSUInteger *)outMask;
+- (NSImage *)_miniwindowImageOfWindow:(NSWindow *)window;
+- (void)_expandWindow:(NSWindow *)window atPoint:(NSPoint)point;
+@end
+
+@implementation PSMTabDragAssistant
+
+static PSMTabDragAssistant *sharedDragAssistant = nil;
+
+#pragma mark -
+#pragma mark Creation/Destruction
+
++ (PSMTabDragAssistant *)sharedDragAssistant
+{
+ if (!sharedDragAssistant) {
+ sharedDragAssistant = [[PSMTabDragAssistant alloc] init];
+ }
+
+ return sharedDragAssistant;
+}
+
+- (id)init
+{
+ if ((self = [super init])) {
+ _sourceTabBar = nil;
+ _destinationTabBar = nil;
+ _participatingTabBars = [[NSMutableSet alloc] init];
+ _draggedCell = nil;
+ _animationTimer = nil;
+ _sineCurveWidths = [[NSMutableArray alloc] initWithCapacity:kPSMTabDragAnimationSteps];
+ _targetCell = nil;
+ _isDragging = NO;
+ }
+
+ return self;
+}
+
+#pragma mark -
+#pragma mark Accessors
+
+- (PSMTabBarControl *)sourceTabBar
+{
+ return _sourceTabBar;
+}
+
+- (void)setSourceTabBar:(PSMTabBarControl *)tabBar
+{
+ _sourceTabBar = tabBar;
+}
+
+- (PSMTabBarControl *)destinationTabBar
+{
+ return _destinationTabBar;
+}
+
+- (void)setDestinationTabBar:(PSMTabBarControl *)tabBar
+{
+ _destinationTabBar = tabBar;
+}
+
+- (PSMTabBarCell *)draggedCell
+{
+ return _draggedCell;
+}
+
+- (void)setDraggedCell:(PSMTabBarCell *)cell
+{
+ _draggedCell = cell;
+}
+
+- (NSInteger)draggedCellIndex
+{
+ return _draggedCellIndex;
+}
+
+- (void)setDraggedCellIndex:(NSInteger)value
+{
+ _draggedCellIndex = value;
+}
+
+- (BOOL)isDragging
+{
+ return _isDragging;
+}
+
+- (void)setIsDragging:(BOOL)value
+{
+ _isDragging = value;
+}
+
+- (NSPoint)currentMouseLoc
+{
+ return _currentMouseLoc;
+}
+
+- (void)setCurrentMouseLoc:(NSPoint)point
+{
+ _currentMouseLoc = point;
+}
+
+- (PSMTabBarCell *)targetCell
+{
+ return _targetCell;
+}
+
+- (void)setTargetCell:(PSMTabBarCell *)cell
+{
+ _targetCell = cell;
+}
+
+#pragma mark -
+#pragma mark Functionality
+
+- (void)startDraggingCell:(PSMTabBarCell *)cell fromTabBar:(PSMTabBarControl *)control withMouseDownEvent:(NSEvent *)event
+{
+ [self setIsDragging:YES];
+ [self setSourceTabBar:control];
+ [self setDestinationTabBar:control];
+ [_participatingTabBars addObject:control];
+ [self setDraggedCell:cell];
+ [self setDraggedCellIndex:[[control cells] indexOfObject:cell]];
+
+ NSRect cellFrame = [cell frame];
+ // list of widths for animation
+ NSInteger i;
+ CGFloat cellStepSize = ([control orientation] == PSMTabBarHorizontalOrientation) ? (cellFrame.size.width + 6) : (cellFrame.size.height + 1);
+ for (i = 0; i < kPSMTabDragAnimationSteps - 1; i++) {
+ NSInteger thisWidth = (NSInteger)(cellStepSize - ((cellStepSize / 2.0) + ((sin((PI / 2.0) + ((CGFloat)i / (CGFloat)kPSMTabDragAnimationSteps) * PI) * cellStepSize) / 2.0)));
+ [_sineCurveWidths addObject:[NSNumber numberWithInteger:thisWidth]];
+ }
+ [_sineCurveWidths addObject:[NSNumber numberWithInteger:([control orientation] == PSMTabBarHorizontalOrientation) ? cellFrame.size.width : cellFrame.size.height]];
+
+ // hide UI buttons
+ [[control overflowPopUpButton] setHidden:YES];
+ [[control addTabButton] setHidden:YES];
+
+ [[NSCursor closedHandCursor] set];
+
+ NSPasteboard *pboard = [NSPasteboard pasteboardWithName:NSDragPboard];
+ NSImage *dragImage = [cell dragImage];
+ [[cell indicator] removeFromSuperview];
+ [self distributePlaceholdersInTabBar:control withDraggedCell:cell];
+
+ if ([control isFlipped]) {
+ cellFrame.origin.y += cellFrame.size.height;
+ }
+ [cell setHighlighted:NO];
+ NSSize offset = NSZeroSize;
+ [pboard declareTypes:[NSArray arrayWithObjects:@"PSMTabBarControlItemPBType", nil] owner:nil];
+ [pboard setString:[[NSNumber numberWithInteger:[[control cells] indexOfObject:cell]] stringValue] forType:@"PSMTabBarControlItemPBType"];
+ _animationTimer = [NSTimer scheduledTimerWithTimeInterval:(1.0 / 30.0) target:self selector:@selector(animateDrag:) userInfo:nil repeats:YES];
+
+ [[NSNotificationCenter defaultCenter] postNotificationName:PSMTabDragDidBeginNotification object:nil];
+
+ //retain the control in case the drag operation causes the control to be released
+
+ if ([control delegate] && [[control delegate] respondsToSelector:@selector(tabView:shouldDropTabViewItem:inTabBar:)] &&
+ [[control delegate] tabView:[control tabView]
+ shouldDropTabViewItem:[[self draggedCell] representedObject]
+ inTabBar:nil]) {
+ _currentTearOffStyle = [control tearOffStyle];
+ _draggedTab = [[PSMTabDragWindowController alloc] initWithImage:dragImage styleMask:NSWindowStyleMaskBorderless tearOffStyle:_currentTearOffStyle];
+
+ cellFrame.origin.y -= cellFrame.size.height;
+ [control dragImage:[[NSImage alloc] initWithSize:NSMakeSize(1, 1)] at:cellFrame.origin offset:offset event:event pasteboard:pboard source:control slideBack:NO];
+ } else {
+ [control dragImage:dragImage at:cellFrame.origin offset:offset event:event pasteboard:pboard source:control slideBack:YES];
+ }
+}
+
+- (void)draggingEnteredTabBar:(PSMTabBarControl *)control atPoint:(NSPoint)mouseLoc
+{
+ if (_currentTearOffStyle == PSMTabBarTearOffMiniwindow && ![self destinationTabBar]) {
+ [_draggedTab switchImages];
+ }
+
+ [self setDestinationTabBar:control];
+ [self setCurrentMouseLoc:mouseLoc];
+ // hide UI buttons
+ [[control overflowPopUpButton] setHidden:YES];
+ [[control addTabButton] setHidden:YES];
+ if ([[control cells] count] == 0 || ![[[control cells] objectAtIndex:0] isPlaceholder]) {
+ [self distributePlaceholdersInTabBar:control];
+ }
+ [_participatingTabBars addObject:control];
+
+ //tell the drag window to display only the header if there is one
+ if (_currentTearOffStyle == PSMTabBarTearOffAlphaWindow && _draggedView) {
+ if (_fadeTimer) {
+ [_fadeTimer invalidate];
+ }
+
+ [[_draggedTab window] orderFront:nil];
+ _fadeTimer = [NSTimer scheduledTimerWithTimeInterval:1.0 / 30.0 target:self selector:@selector(fadeOutDragWindow:) userInfo:nil repeats:YES];
+ }
+}
+
+- (void)draggingUpdatedInTabBar:(PSMTabBarControl *)control atPoint:(NSPoint)mouseLoc
+{
+ if ([self destinationTabBar] != control) {
+ [self setDestinationTabBar:control];
+ }
+ [self setCurrentMouseLoc:mouseLoc];
+}
+
+- (void)draggingExitedTabBar:(PSMTabBarControl *)control
+{
+ if ([[control delegate] respondsToSelector:@selector(tabView:shouldAllowTabViewItem:toLeaveTabBar:)] && ![[control delegate] tabView:[control tabView] shouldAllowTabViewItem:[[self draggedCell] representedObject] toLeaveTabBar:control]) {
+ return;
+ }
+
+ [self setDestinationTabBar:nil];
+ [self setCurrentMouseLoc:NSMakePoint(-1.0, -1.0)];
+
+ if (_fadeTimer) {
+ [_fadeTimer invalidate];
+ _fadeTimer = [NSTimer scheduledTimerWithTimeInterval:1.0 / 30.0 target:self selector:@selector(fadeInDragWindow:) userInfo:nil repeats:YES];
+ } else if (_draggedTab) {
+ if (_currentTearOffStyle == PSMTabBarTearOffAlphaWindow) {
+ //create a new floating drag window
+ if (!_draggedView) {
+ NSUInteger styleMask;
+ NSImage *viewImage = [self _imageForViewOfCell:[self draggedCell] styleMask:&styleMask];
+
+ _draggedView = [[PSMTabDragWindowController alloc] initWithImage:viewImage styleMask:styleMask tearOffStyle:PSMTabBarTearOffAlphaWindow];
+ [[_draggedView window] setAlphaValue:0.0];
+ }
+
+ NSPoint windowOrigin = [[control window] frame].origin;
+
+ windowOrigin.x -= _dragWindowOffset.width;
+ windowOrigin.y += _dragWindowOffset.height;
+ [[_draggedView window] setFrameOrigin:windowOrigin];
+ [[_draggedView window] orderWindow:NSWindowBelow relativeTo:[[_draggedTab window] windowNumber]];
+ } else if (_currentTearOffStyle == PSMTabBarTearOffMiniwindow && ![_draggedTab alternateImage]) {
+ NSImage *image;
+ NSSize imageSize;
+ NSUInteger mask; //we don't need this but we can't pass nil in for the style mask, as some delegate implementations will crash
+
+ if (!(image = [self _miniwindowImageOfWindow:[control window]])) {
+ image = [[self _imageForViewOfCell:[self draggedCell] styleMask:&mask] copy];
+ }
+
+ imageSize = [image size];
+ [image setScalesWhenResized:YES];
+
+ if (imageSize.width > imageSize.height) {
+ [image setSize:NSMakeSize(125, 125 * (imageSize.height / imageSize.width))];
+ } else {
+ [image setSize:NSMakeSize(125 * (imageSize.width / imageSize.height), 125)];
+ }
+
+ [_draggedTab setAlternateImage:image];
+ }
+
+ //set the window's alpha mask to zero if the last tab is being dragged
+ //don't fade out the old window if the delegate doesn't respond to the new tab bar method, just to be safe
+ if ([[[self sourceTabBar] tabView] numberOfTabViewItems] == 1 && [self sourceTabBar] == control &&
+ [[[self sourceTabBar] delegate] respondsToSelector:@selector(tabView:newTabBarForDraggedTabViewItem:atPoint:)]) {
+ [[[self sourceTabBar] window] setAlphaValue:0.0];
+
+ if ([_sourceTabBar tearOffStyle] == PSMTabBarTearOffAlphaWindow) {
+ [[_draggedView window] setAlphaValue:kPSMTabDragWindowAlpha];
+ } else {
+ //#warning fix me - what should we do when the last tab is dragged as a miniwindow?
+ }
+ } else {
+ if ([_sourceTabBar tearOffStyle] == PSMTabBarTearOffAlphaWindow) {
+ _fadeTimer = [NSTimer scheduledTimerWithTimeInterval:1.0 / 30.0 target:self selector:@selector(fadeInDragWindow:) userInfo:nil repeats:YES];
+ } else {
+ [_draggedTab switchImages];
+ _centersDragWindows = YES;
+ }
+ }
+ }
+}
+
+- (void)performDragOperation
+{
+ // move cell
+ NSUInteger destinationIndex = [[[self destinationTabBar] cells] indexOfObject:[self targetCell]];
+
+ //there is the slight possibility of the targetCell now being set properly, so avoid errors
+ if (destinationIndex >= [[[self destinationTabBar] cells] count]) {
+ destinationIndex = [[[self destinationTabBar] cells] count] - 1;
+ }
+
+ [[[self destinationTabBar] cells] replaceObjectAtIndex:destinationIndex withObject:[self draggedCell]];
+ [[self draggedCell] setControlView:[self destinationTabBar]];
+
+ // move actual NSTabViewItem
+ if ([self sourceTabBar] != [self destinationTabBar]) {
+ //remove the tracking rects and bindings registered on the old tab
+ [[self sourceTabBar] removeTrackingRect:[[self draggedCell] closeButtonTrackingTag]];
+ [[self sourceTabBar] removeTrackingRect:[[self draggedCell] cellTrackingTag]];
+ [[self sourceTabBar] removeTabForCell:[self draggedCell]];
+
+ NSUInteger i, insertIndex;
+ NSArray *cells = [[self destinationTabBar] cells];
+
+ //find the index of where the dragged cell was just dropped
+ for (i = 0, insertIndex = 0; (i < [cells count]) && ([cells objectAtIndex:i] != [self draggedCell]); i++, insertIndex++) {
+ if ([[cells objectAtIndex:i] isPlaceholder]) {
+ insertIndex--;
+ }
+ }
+
+ [[[self sourceTabBar] tabView] removeTabViewItem:[[self draggedCell] representedObject]];
+ [[[self destinationTabBar] tabView] insertTabViewItem:[[self draggedCell] representedObject] atIndex:insertIndex];
+
+ //calculate the position for the dragged cell
+ if ([[self destinationTabBar] automaticallyAnimates]) {
+ if (insertIndex > 0) {
+ NSRect cellRect = [[cells objectAtIndex:insertIndex - 1] frame];
+ cellRect.origin.x += cellRect.size.width;
+ [[self draggedCell] setFrame:cellRect];
+ }
+ }
+
+ //rebind the cell to the new control
+ [[self destinationTabBar] bindPropertiesForCell:[self draggedCell] andTabViewItem:[[self draggedCell] representedObject]];
+
+ //select the newly moved item in the destination tab view
+ [[[self destinationTabBar] tabView] selectTabViewItem:[[self draggedCell] representedObject]];
+ } else {
+ //have to do this before checking the index of a cell otherwise placeholders will be counted
+ [self removeAllPlaceholdersFromTabBar:[self sourceTabBar]];
+
+ //rearrange the tab view items
+ NSTabView *tabView = [[self sourceTabBar] tabView];
+ NSTabViewItem *item = [[self draggedCell] representedObject];
+ BOOL reselect = ([tabView selectedTabViewItem] == item);
+ NSArray *cells = [[self sourceTabBar] cells];
+ NSUInteger index;
+ //find the index of where the dragged cell was just dropped
+ for (index = 0; index < [cells count] && [cells objectAtIndex:index] != [self draggedCell]; index++) {
+ ;
+ }
+
+ //temporarily disable the delegate in order to move the tab to a different index
+ id tempDelegate = [tabView delegate];
+ [tabView setDelegate:nil];
+ [tabView removeTabViewItem:item];
+ [tabView insertTabViewItem:item atIndex:index];
+ if (reselect) {
+ [tabView selectTabViewItem:item];
+ }
+ [tabView setDelegate:tempDelegate];
+ }
+
+ if (([self sourceTabBar] != [self destinationTabBar] || [[[self sourceTabBar] cells] indexOfObject:[self draggedCell]] != _draggedCellIndex) && [[[self sourceTabBar] delegate] respondsToSelector:@selector(tabView:didDropTabViewItem:inTabBar:)]) {
+ [[[self sourceTabBar] delegate] tabView:[[self sourceTabBar] tabView] didDropTabViewItem:[[self draggedCell] representedObject] inTabBar:[self destinationTabBar]];
+ }
+
+ [[NSNotificationCenter defaultCenter] postNotificationName:PSMTabDragDidEndNotification object:nil];
+
+ [self finishDrag];
+}
+
+- (void)draggedImageEndedAt:(NSPoint)aPoint operation:(NSDragOperation)operation
+{
+ if ([self isDragging]) { // means there was not a successful drop (performDragOperation)
+ id sourceDelegate = [[self sourceTabBar] delegate];
+
+ //split off the dragged tab into a new window
+ if ([self destinationTabBar] == nil && sourceDelegate && [sourceDelegate respondsToSelector:@selector(tabView:shouldDropTabViewItem:inTabBar:)] &&
+ [sourceDelegate tabView:[[self sourceTabBar] tabView]
+ shouldDropTabViewItem:[[self draggedCell] representedObject]
+ inTabBar:nil]
+ &&
+ [sourceDelegate respondsToSelector:@selector(tabView:newTabBarForDraggedTabViewItem:atPoint:)]) {
+ PSMTabBarControl *control = [sourceDelegate tabView:[[self sourceTabBar] tabView] newTabBarForDraggedTabViewItem:[[self draggedCell] representedObject] atPoint:aPoint];
+
+ if (control) {
+ //add the dragged tab to the new window
+ [[control cells] insertObject:[self draggedCell] atIndex:0];
+
+ //remove the tracking rects and bindings registered on the old tab
+ [[self sourceTabBar] removeTrackingRect:[[self draggedCell] closeButtonTrackingTag]];
+ [[self sourceTabBar] removeTrackingRect:[[self draggedCell] cellTrackingTag]];
+ [[self sourceTabBar] removeTabForCell:[self draggedCell]];
+
+ //rebind the cell to the new control
+ [control bindPropertiesForCell:[self draggedCell] andTabViewItem:[[self draggedCell] representedObject]];
+
+ [[self draggedCell] setControlView:control];
+
+ [[[self sourceTabBar] tabView] removeTabViewItem:[[self draggedCell] representedObject]];
+
+ [[control tabView] addTabViewItem:[[self draggedCell] representedObject]];
+ [control update:NO]; //make sure the new tab is set in the correct position
+
+ if (_currentTearOffStyle == PSMTabBarTearOffAlphaWindow) {
+ [[control window] makeKeyAndOrderFront:nil];
+ } else {
+ //center the window over where we ended dragging
+ [self _expandWindow:[control window] atPoint:[NSEvent mouseLocation]];
+ }
+
+ if ([sourceDelegate respondsToSelector:@selector(tabView:didDropTabViewItem:inTabBar:)]) {
+ [sourceDelegate tabView:[[self sourceTabBar] tabView] didDropTabViewItem:[[self draggedCell] representedObject] inTabBar:control];
+ }
+ } else {
+ NSLog(@"Delegate returned no control to add to.");
+ [[[self sourceTabBar] cells] insertObject:[self draggedCell] atIndex:[self draggedCellIndex]];
+ }
+ } else {
+ // put cell back
+ [[[self sourceTabBar] cells] insertObject:[self draggedCell] atIndex:[self draggedCellIndex]];
+ }
+
+ [[NSNotificationCenter defaultCenter] postNotificationName:PSMTabDragDidEndNotification object:nil];
+
+ [self finishDrag];
+ }
+}
+
+- (void)finishDrag
+{
+ if ([[[self sourceTabBar] tabView] numberOfTabViewItems] == 0 && [[[self sourceTabBar] delegate] respondsToSelector:@selector(tabView:closeWindowForLastTabViewItem:)]) {
+ [[[self sourceTabBar] delegate] tabView:[[self sourceTabBar] tabView] closeWindowForLastTabViewItem:[[self draggedCell] representedObject]];
+ }
+
+ if (_draggedTab) {
+ [[_draggedTab window] orderOut:nil];
+ _draggedTab = nil;
+ }
+
+ if (_draggedView) {
+ [[_draggedView window] orderOut:nil];
+ _draggedView = nil;
+ }
+
+ _centersDragWindows = NO;
+
+ [self setIsDragging:NO];
+ [self removeAllPlaceholdersFromTabBar:[self sourceTabBar]];
+ [self setSourceTabBar:nil];
+ [self setDestinationTabBar:nil];
+ NSEnumerator *e = [_participatingTabBars objectEnumerator];
+ PSMTabBarControl *tabBar;
+ while ((tabBar = [e nextObject])) {
+ [self removeAllPlaceholdersFromTabBar:tabBar];
+ }
+ [_participatingTabBars removeAllObjects];
+ [self setDraggedCell:nil];
+ [_animationTimer invalidate];
+ _animationTimer = nil;
+ [_sineCurveWidths removeAllObjects];
+ [self setTargetCell:nil];
+}
+
+- (void)draggingBeganAt:(NSPoint)aPoint
+{
+ if (_draggedTab) {
+ [[_draggedTab window] setFrameTopLeftPoint:aPoint];
+ [[_draggedTab window] orderFront:nil];
+
+ if ([[[self sourceTabBar] tabView] numberOfTabViewItems] == 1) {
+ [self draggingExitedTabBar:[self sourceTabBar]];
+ [[_draggedTab window] setAlphaValue:0.0];
+ }
+ }
+}
+
+- (void)draggingMovedTo:(NSPoint)aPoint
+{
+ if (_draggedTab) {
+ if (_centersDragWindows) {
+ if ([_draggedTab isAnimating]) {
+ return;
+ }
+
+ //Ignore aPoint, as it seems to give wacky values
+ NSRect frame = [[_draggedTab window] frame];
+ frame.origin = [NSEvent mouseLocation];
+ frame.origin.x -= frame.size.width / 2;
+ frame.origin.y -= frame.size.height / 2;
+ [[_draggedTab window] setFrame:frame display:NO];
+ } else {
+ [[_draggedTab window] setFrameTopLeftPoint:aPoint];
+ }
+
+ if (_draggedView) {
+ //move the view representation with the tab
+ //the relative position of the dragged view window will be different
+ //depending on the position of the tab bar relative to the controlled tab view
+
+ aPoint.y -= [[_draggedTab window] frame].size.height;
+ aPoint.x -= _dragWindowOffset.width;
+ aPoint.y += _dragWindowOffset.height;
+ [[_draggedView window] setFrameTopLeftPoint:aPoint];
+ }
+ }
+}
+
+- (void)fadeInDragWindow:(NSTimer *)timer
+{
+ CGFloat value = [[_draggedView window] alphaValue];
+ if (value >= kPSMTabDragWindowAlpha || _draggedTab == nil) {
+ [timer invalidate];
+ _fadeTimer = nil;
+ } else {
+ [[_draggedTab window] setAlphaValue:[[_draggedTab window] alphaValue] - kPSMTabDragAlphaInterval];
+ [[_draggedView window] setAlphaValue:value + kPSMTabDragAlphaInterval];
+ }
+}
+
+- (void)fadeOutDragWindow:(NSTimer *)timer
+{
+ CGFloat value = [[_draggedView window] alphaValue];
+ NSWindow *tabWindow = [_draggedTab window], *viewWindow = [_draggedView window];
+
+ if (value <= 0.0) {
+ [viewWindow setAlphaValue:0.0];
+ [tabWindow setAlphaValue:kPSMTabDragWindowAlpha];
+
+ [timer invalidate];
+ _fadeTimer = nil;
+ } else {
+ if ([tabWindow alphaValue] < kPSMTabDragWindowAlpha) {
+ [tabWindow setAlphaValue:[tabWindow alphaValue] + kPSMTabDragAlphaInterval];
+ }
+ [viewWindow setAlphaValue:value - kPSMTabDragAlphaInterval];
+ }
+}
+
+#pragma mark -
+#pragma mark Private
+
+- (NSImage *)_imageForViewOfCell:(PSMTabBarCell *)cell styleMask:(NSUInteger *)outMask
+{
+ PSMTabBarControl *control = [cell controlView];
+ NSImage *viewImage = nil;
+
+ if (outMask) {
+ *outMask = NSWindowStyleMaskBorderless;
+ }
+
+ if ([control delegate] && [[control delegate] respondsToSelector:@selector(tabView:imageForTabViewItem:offset:styleMask:)]) {
+ //get a custom image representation of the view to drag from the delegate
+ NSImage *tabImage = [_draggedTab image];
+ NSPoint drawPoint;
+ _dragWindowOffset = NSZeroSize;
+ viewImage = [[control delegate] tabView:[control tabView] imageForTabViewItem:[cell representedObject] offset:&_dragWindowOffset styleMask:outMask];
+ [viewImage lockFocus];
+
+ //draw the tab into the returned window, that way we don't have two windows being dragged (this assumes the tab will be on the window)
+ drawPoint = NSMakePoint(_dragWindowOffset.width, [viewImage size].height - _dragWindowOffset.height);
+
+ if ([control orientation] == PSMTabBarHorizontalOrientation) {
+ drawPoint.y += [[control style] tabCellHeight] - [tabImage size].height;
+ _dragWindowOffset.height -= [[control style] tabCellHeight] - [tabImage size].height;
+ } else {
+ drawPoint.x += [control frame].size.width - [tabImage size].width;
+ }
+
+ [tabImage drawAtPoint:drawPoint fromRect:NSZeroRect operation:NSCompositingOperationSourceOver fraction:1.0];
+
+ [viewImage unlockFocus];
+ } else {
+ //the delegate doesn't give a custom image, so use an image of the view
+ NSView *tabView = [[cell representedObject] view];
+ viewImage = [[NSImage alloc] initWithSize:[tabView frame].size];
+ [viewImage lockFocus];
+ [tabView drawRect:[tabView bounds]];
+ [viewImage unlockFocus];
+ }
+
+ if (*outMask | NSWindowStyleMaskBorderless) {
+ _dragWindowOffset.height += 22;
+ }
+
+ return viewImage;
+}
+
+- (NSImage *)_miniwindowImageOfWindow:(NSWindow *)window
+{
+ NSRect rect = [window frame];
+ NSImage *image = [[NSImage alloc] initWithSize:rect.size];
+ [image lockFocus];
+ rect.origin = NSZeroPoint;
+ CGContextCopyWindowCaptureContentsToRect([[NSGraphicsContext currentContext] graphicsPort], *(CGRect *)&rect, [NSApp contextID], [window windowNumber], 0);
+ [image unlockFocus];
+
+ return image;
+}
+
+- (void)_expandWindow:(NSWindow *)window atPoint:(NSPoint)point
+{
+ NSRect frame = [window frame];
+ [window setFrameTopLeftPoint:NSMakePoint(point.x - frame.size.width / 2, point.y + frame.size.height / 2)];
+ [window setAlphaValue:0.0];
+ [window makeKeyAndOrderFront:nil];
+
+ NSAnimation *animation = [[NSAnimation alloc] initWithDuration:0.25 animationCurve:NSAnimationEaseInOut];
+ [animation setAnimationBlockingMode:NSAnimationNonblocking];
+ [animation setCurrentProgress:0.1];
+ [animation startAnimation];
+ NSTimer *timer = [NSTimer scheduledTimerWithTimeInterval:1.0 / 30.0 target:self selector:@selector(_expandWindowTimerFired:) userInfo:[NSDictionary dictionaryWithObjectsAndKeys:window, @"Window", animation, @"Animation", nil] repeats:YES];
+ [[NSRunLoop currentRunLoop] addTimer:timer forMode:NSEventTrackingRunLoopMode];
+}
+
+- (void)_expandWindowTimerFired:(NSTimer *)timer
+{
+ NSWindow *window = [[timer userInfo] objectForKey:@"Window"];
+ NSAnimation *animation = [[timer userInfo] objectForKey:@"Animation"];
+ CGAffineTransform transform;
+ NSPoint translation;
+ NSRect winFrame = [window frame];
+
+ translation.x = (winFrame.size.width / 2.0);
+ translation.y = (winFrame.size.height / 2.0);
+ transform = CGAffineTransformMakeTranslation(translation.x, translation.y);
+ transform = CGAffineTransformScale(transform, 1.0 / [animation currentValue], 1.0 / [animation currentValue]);
+ transform = CGAffineTransformTranslate(transform, -translation.x, -translation.y);
+
+ translation.x = -winFrame.origin.x;
+ translation.y = winFrame.origin.y + winFrame.size.height - [[NSScreen mainScreen] frame].size.height;
+
+ transform = CGAffineTransformTranslate(transform, translation.x, translation.y);
+
+ CGSSetWindowTransform([NSApp contextID], [window windowNumber], transform);
+
+ [window setAlphaValue:[animation currentValue]];
+
+ if (![animation isAnimating]) {
+ [timer invalidate];
+ }
+}
+
+#pragma mark -
+#pragma mark Animation
+
+- (void)animateDrag:(NSTimer *)timer
+{
+ NSEnumerator *e = [[_participatingTabBars copy] objectEnumerator];
+ PSMTabBarControl *tabBar;
+ while ((tabBar = [e nextObject])) {
+ [self calculateDragAnimationForTabBar:tabBar];
+ [[NSRunLoop currentRunLoop] performSelector:@selector(display) target:tabBar argument:nil order:1 modes:[NSArray arrayWithObjects:@"NSEventTrackingRunLoopMode", @"NSDefaultRunLoopMode", nil]];
+ }
+}
+
+- (void)calculateDragAnimationForTabBar:(PSMTabBarControl *)control
+{
+ BOOL removeFlag = YES;
+ NSMutableArray *cells = [control cells];
+ NSInteger i, cellCount = [cells count];
+ CGFloat position = [control orientation] == PSMTabBarHorizontalOrientation ? [[control style] leftMarginForTabBarControl] : [[control style] topMarginForTabBarControl];
+
+ // identify target cell
+ // mouse at beginning of tabs
+ NSPoint mouseLoc = [self currentMouseLoc];
+ if ([self destinationTabBar] == control) {
+ removeFlag = NO;
+ if (mouseLoc.x < [[control style] leftMarginForTabBarControl]) {
+ [self setTargetCell:[cells objectAtIndex:0]];
+ } else {
+ NSRect overCellRect;
+ PSMTabBarCell *overCell = [control cellForPoint:mouseLoc cellFrame:&overCellRect];
+ if (overCell) {
+ // mouse among cells - placeholder
+ if ([overCell isPlaceholder]) {
+ [self setTargetCell:overCell];
+ } else if ([control orientation] == PSMTabBarHorizontalOrientation) {
+ // non-placeholders - horizontal orientation
+ if (mouseLoc.x < (overCellRect.origin.x + (overCellRect.size.width / 2.0))) {
+ // mouse on left side of cell
+ [self setTargetCell:[cells objectAtIndex:([cells indexOfObject:overCell] - 1)]];
+ } else {
+ // mouse on right side of cell
+ [self setTargetCell:[cells objectAtIndex:([cells indexOfObject:overCell] + 1)]];
+ }
+ } else {
+ // non-placeholders - vertical orientation
+ if (mouseLoc.y < (overCellRect.origin.y + (overCellRect.size.height / 2.0))) {
+ // mouse on top of cell
+ [self setTargetCell:[cells objectAtIndex:([cells indexOfObject:overCell] - 1)]];
+ } else {
+ // mouse on bottom of cell
+ [self setTargetCell:[cells objectAtIndex:([cells indexOfObject:overCell] + 1)]];
+ }
+ }
+ } else {
+ // out at end - must find proper cell (could be more in overflow menu)
+ [self setTargetCell:[control lastVisibleTab]];
+ }
+ }
+ } else {
+ [self setTargetCell:nil];
+ }
+
+ for (i = 0; i < cellCount; i++) {
+ PSMTabBarCell *cell = [cells objectAtIndex:i];
+ NSRect newRect = [cell frame];
+ if (![cell isInOverflowMenu]) {
+ if ([cell isPlaceholder]) {
+ if (cell == [self targetCell]) {
+ [cell setCurrentStep:([cell currentStep] + 1)];
+ } else {
+ [cell setCurrentStep:([cell currentStep] - 1)];
+ if ([cell currentStep] > 0) {
+ removeFlag = NO;
+ }
+ }
+
+ if ([control orientation] == PSMTabBarHorizontalOrientation) {
+ newRect.size.width = [[_sineCurveWidths objectAtIndex:[cell currentStep]] integerValue];
+ } else {
+ newRect.size.height = [[_sineCurveWidths objectAtIndex:[cell currentStep]] integerValue];
+ }
+ }
+ } else {
+ break;
+ }
+
+ if ([control orientation] == PSMTabBarHorizontalOrientation) {
+ newRect.origin.x = position;
+ position += newRect.size.width;
+ } else {
+ newRect.origin.y = position;
+ position += newRect.size.height;
+ }
+ [cell setFrame:newRect];
+ if ([cell indicator]) {
+ [[cell indicator] setFrame:[[control style] indicatorRectForTabCell:cell]];
+ }
+ }
+ if (removeFlag) {
+ [_participatingTabBars removeObject:control];
+ [self removeAllPlaceholdersFromTabBar:control];
+ }
+}
+
+#pragma mark -
+#pragma mark Placeholders
+
+- (void)distributePlaceholdersInTabBar:(PSMTabBarControl *)control withDraggedCell:(PSMTabBarCell *)cell
+{
+ // called upon first drag - must distribute placeholders
+ [self distributePlaceholdersInTabBar:control];
+
+ NSMutableArray *cells = [control cells];
+
+ // replace dragged cell with a placeholder, and clean up surrounding cells
+ NSInteger cellIndex = [cells indexOfObject:cell];
+ PSMTabBarCell *pc = [[PSMTabBarCell alloc] initPlaceholderWithFrame:[[self draggedCell] frame] expanded:YES inControlView:control];
+ [cells replaceObjectAtIndex:cellIndex withObject:pc];
+ [cells removeObjectAtIndex:(cellIndex + 1)];
+ [cells removeObjectAtIndex:(cellIndex - 1)];
+
+ if (cellIndex - 2 >= 0) {
+ pc = [cells objectAtIndex:cellIndex - 2];
+ [pc setTabState:~[pc tabState] & PSMTab_RightIsSelectedMask];
+ }
+}
+
+- (void)distributePlaceholdersInTabBar:(PSMTabBarControl *)control
+{
+ NSUInteger i, numVisibleTabs = [control numberOfVisibleTabs];
+ for (i = 0; i < numVisibleTabs; i++) {
+ PSMTabBarCell *pc = [[PSMTabBarCell alloc] initPlaceholderWithFrame:[[self draggedCell] frame] expanded:NO inControlView:control];
+ [[control cells] insertObject:pc atIndex:(2 * i)];
+ }
+
+ PSMTabBarCell *pc = [[PSMTabBarCell alloc] initPlaceholderWithFrame:[[self draggedCell] frame] expanded:NO inControlView:control];
+ if ([[control cells] count] > (2 * numVisibleTabs)) {
+ [[control cells] insertObject:pc atIndex:(2 * numVisibleTabs)];
+ } else {
+ [[control cells] addObject:pc];
+ }
+}
+
+- (void)removeAllPlaceholdersFromTabBar:(PSMTabBarControl *)control
+{
+ NSInteger i, cellCount = [[control cells] count];
+ for (i = (cellCount - 1); i >= 0; i--) {
+ PSMTabBarCell *cell = [[control cells] objectAtIndex:i];
+ if ([cell isPlaceholder]) {
+ [control removeTabForCell:cell];
+ }
+ }
+ // redraw
+ [control update:NO];
+}
+
+#pragma mark -
+#pragma mark Archiving
+
+- (void)encodeWithCoder:(NSCoder *)aCoder
+{
+ //[super encodeWithCoder:aCoder];
+ if ([aCoder allowsKeyedCoding]) {
+ [aCoder encodeObject:_sourceTabBar forKey:@"sourceTabBar"];
+ [aCoder encodeObject:_destinationTabBar forKey:@"destinationTabBar"];
+ [aCoder encodeObject:_participatingTabBars forKey:@"participatingTabBars"];
+ [aCoder encodeObject:_draggedCell forKey:@"draggedCell"];
+ [aCoder encodeInteger:_draggedCellIndex forKey:@"draggedCellIndex"];
+ [aCoder encodeBool:_isDragging forKey:@"isDragging"];
+ [aCoder encodeObject:_animationTimer forKey:@"animationTimer"];
+ [aCoder encodeObject:_sineCurveWidths forKey:@"sineCurveWidths"];
+ [aCoder encodePoint:_currentMouseLoc forKey:@"currentMouseLoc"];
+ [aCoder encodeObject:_targetCell forKey:@"targetCell"];
+ }
+}
+
+- (id)initWithCoder:(NSCoder *)aDecoder
+{
+ //self = [super initWithCoder:aDecoder];
+ //if (self) {
+ if ([aDecoder allowsKeyedCoding]) {
+ _sourceTabBar = [aDecoder decodeObjectForKey:@"sourceTabBar"];
+ _destinationTabBar = [aDecoder decodeObjectForKey:@"destinationTabBar"];
+ _participatingTabBars = [aDecoder decodeObjectForKey:@"participatingTabBars"];
+ _draggedCell = [aDecoder decodeObjectForKey:@"draggedCell"];
+ _draggedCellIndex = [aDecoder decodeIntegerForKey:@"draggedCellIndex"];
+ _isDragging = [aDecoder decodeBoolForKey:@"isDragging"];
+ _animationTimer = [aDecoder decodeObjectForKey:@"animationTimer"];
+ _sineCurveWidths = [aDecoder decodeObjectForKey:@"sineCurveWidths"];
+ _currentMouseLoc = [aDecoder decodePointForKey:@"currentMouseLoc"];
+ _targetCell = [aDecoder decodeObjectForKey:@"targetCell"];
+ }
+ //}
+ return self;
+}
+
+@end
diff --git a/frontends/cocoa/PSMTabBarControl/PSMTabDragView.h b/frontends/cocoa/PSMTabBarControl/PSMTabDragView.h
new file mode 100644
index 000000000..d106f5106
--- /dev/null
+++ b/frontends/cocoa/PSMTabBarControl/PSMTabDragView.h
@@ -0,0 +1,21 @@
+//
+// PSMTabDragView.h
+// PSMTabBarControl
+//
+// Created by Kent Sutherland on 6/17/07.
+// Copyright 2007 Kent Sutherland. All rights reserved.
+//
+
+#import <Cocoa/Cocoa.h>
+
+@interface PSMTabDragView : NSView {
+ NSImage *_image;
+ NSImage *_alternateImage;
+ CGFloat _alpha;
+}
+- (void)setFadeValue:(CGFloat)value;
+- (NSImage *)image;
+- (void)setImage:(NSImage *)image;
+- (NSImage *)alternateImage;
+- (void)setAlternateImage:(NSImage *)image;
+@end
diff --git a/frontends/cocoa/PSMTabBarControl/PSMTabDragView.m b/frontends/cocoa/PSMTabBarControl/PSMTabDragView.m
new file mode 100644
index 000000000..4f6ce9131
--- /dev/null
+++ b/frontends/cocoa/PSMTabBarControl/PSMTabDragView.m
@@ -0,0 +1,60 @@
+//
+// PSMTabDragView.m
+// PSMTabBarControl
+//
+// Created by Kent Sutherland on 6/17/07.
+// Copyright 2007 Kent Sutherland. All rights reserved.
+//
+
+#import "PSMTabDragView.h"
+
+@implementation PSMTabDragView
+
+- (id)initWithFrame:(NSRect)frame
+{
+ if ((self = [super initWithFrame:frame])) {
+ _alpha = 1.0;
+ }
+ return self;
+}
+
+- (void)drawRect:(NSRect)rect
+{
+ //1.0 fade means show the primary image
+ //0.0 fade means show the secondary image
+ CGFloat primaryAlpha = _alpha + 0.001f, alternateAlpha = 1.001f - _alpha;
+ NSRect srcRect;
+ srcRect.origin = NSZeroPoint;
+ srcRect.size = [_image size];
+
+ [_image drawInRect:[self bounds] fromRect:srcRect operation:NSCompositingOperationSourceOver fraction:primaryAlpha];
+ srcRect.size = [_alternateImage size];
+ [_alternateImage drawInRect:[self bounds] fromRect:srcRect operation:NSCompositingOperationSourceOver fraction:alternateAlpha];
+}
+
+- (void)setFadeValue:(CGFloat)value
+{
+ _alpha = value;
+}
+
+- (NSImage *)image
+{
+ return _image;
+}
+
+- (void)setImage:(NSImage *)image
+{
+ _image = image;
+}
+
+- (NSImage *)alternateImage
+{
+ return _alternateImage;
+}
+
+- (void)setAlternateImage:(NSImage *)image
+{
+ _alternateImage = image;
+}
+
+@end
diff --git a/frontends/cocoa/PSMTabBarControl/PSMTabDragWindow.h b/frontends/cocoa/PSMTabBarControl/PSMTabDragWindow.h
new file mode 100644
index 000000000..15d066236
--- /dev/null
+++ b/frontends/cocoa/PSMTabBarControl/PSMTabDragWindow.h
@@ -0,0 +1,20 @@
+//
+// PSMTabDragWindow.h
+// PSMTabBarControl
+//
+// Created by Kent Sutherland on 6/1/06.
+// Copyright 2006 Kent Sutherland. All rights reserved.
+//
+
+#import <Cocoa/Cocoa.h>
+
+@class PSMTabDragView;
+
+@interface PSMTabDragWindow : NSWindow {
+ PSMTabDragView *_dragView;
+}
++ (PSMTabDragWindow *)dragWindowWithImage:(NSImage *)image styleMask:(NSUInteger)styleMask;
+
+- (id)initWithImage:(NSImage *)image styleMask:(NSUInteger)styleMask;
+- (PSMTabDragView *)dragView;
+@end
diff --git a/frontends/cocoa/PSMTabBarControl/PSMTabDragWindow.m b/frontends/cocoa/PSMTabBarControl/PSMTabDragWindow.m
new file mode 100644
index 000000000..cae9280f1
--- /dev/null
+++ b/frontends/cocoa/PSMTabBarControl/PSMTabDragWindow.m
@@ -0,0 +1,51 @@
+//
+// PSMTabDragWindow.m
+// PSMTabBarControl
+//
+// Created by Kent Sutherland on 6/1/06.
+// Copyright 2006 Kent Sutherland. All rights reserved.
+//
+
+#import "PSMTabDragWindow.h"
+#import "PSMTabDragView.h"
+
+@implementation PSMTabDragWindow
+
++ (PSMTabDragWindow *)dragWindowWithImage:(NSImage *)image styleMask:(NSUInteger)styleMask
+{
+ return [[PSMTabDragWindow alloc] initWithImage:image styleMask:styleMask];
+}
+
+- (id)initWithImage:(NSImage *)image styleMask:(NSUInteger)styleMask
+{
+ NSSize size = [image size];
+
+ if ((self = [super initWithContentRect:NSMakeRect(0, 0, size.width, size.height) styleMask:styleMask backing:NSBackingStoreBuffered defer:NO])) {
+ _dragView = [[PSMTabDragView alloc] initWithFrame:NSMakeRect(0, 0, size.width, size.height)];
+ [self setContentView:_dragView];
+ [self setLevel:NSStatusWindowLevel];
+ [self setIgnoresMouseEvents:YES];
+ [self setOpaque:NO];
+
+ [_dragView setImage:image];
+
+ //Set the size of the window to be the exact size of the drag image
+ NSRect windowFrame = [self frame];
+ windowFrame.origin.y += windowFrame.size.height - size.height;
+ windowFrame.size = size;
+
+ if (styleMask | NSWindowStyleMaskBorderless) {
+ windowFrame.size.height += 22;
+ }
+
+ [self setFrame:windowFrame display:YES];
+ }
+ return self;
+}
+
+- (PSMTabDragView *)dragView
+{
+ return _dragView;
+}
+
+@end
diff --git a/frontends/cocoa/PSMTabBarControl/PSMTabDragWindowController.h b/frontends/cocoa/PSMTabBarControl/PSMTabDragWindowController.h
new file mode 100644
index 000000000..bc67622e7
--- /dev/null
+++ b/frontends/cocoa/PSMTabBarControl/PSMTabDragWindowController.h
@@ -0,0 +1,33 @@
+//
+// PSMTabDragWindowController.h
+// PSMTabBarControl
+//
+// Created by Kent Sutherland on 6/18/07.
+// Copyright 2007 Kent Sutherland. All rights reserved.
+//
+
+#import <Cocoa/Cocoa.h>
+#import "PSMTabBarControl.h"
+
+#define kPSMTabDragWindowAlpha 0.75
+#define kPSMTabDragAlphaInterval 0.15
+
+@class PSMTabDragView;
+
+@interface PSMTabDragWindowController : NSWindowController {
+ PSMTabBarTearOffStyle _tearOffStyle;
+ PSMTabDragView *_view;
+ NSAnimation *_animation;
+ NSTimer *_timer;
+
+ BOOL _showingAlternate;
+ NSRect _originalWindowFrame;
+}
+- (id)initWithImage:(NSImage *)image styleMask:(NSUInteger)styleMask tearOffStyle:(PSMTabBarTearOffStyle)tearOffStyle;
+
+- (NSImage *)image;
+- (NSImage *)alternateImage;
+- (void)setAlternateImage:(NSImage *)image;
+- (BOOL)isAnimating;
+- (void)switchImages;
+@end
diff --git a/frontends/cocoa/PSMTabBarControl/PSMTabDragWindowController.m b/frontends/cocoa/PSMTabBarControl/PSMTabDragWindowController.m
new file mode 100644
index 000000000..78b0fcb2c
--- /dev/null
+++ b/frontends/cocoa/PSMTabBarControl/PSMTabDragWindowController.m
@@ -0,0 +1,109 @@
+//
+// PSMTabDragWindowController.m
+// PSMTabBarControl
+//
+// Created by Kent Sutherland on 6/18/07.
+// Copyright 2007 Kent Sutherland. All rights reserved.
+//
+
+#import "PSMTabDragWindowController.h"
+#import "PSMTabDragWindow.h"
+#import "PSMTabDragView.h"
+
+@implementation PSMTabDragWindowController
+
+- (id)initWithImage:(NSImage *)image styleMask:(NSUInteger)styleMask tearOffStyle:(PSMTabBarTearOffStyle)tearOffStyle
+{
+ PSMTabDragWindow *window = [PSMTabDragWindow dragWindowWithImage:image styleMask:styleMask];
+ if ((self = [super initWithWindow:window])) {
+ _view = [window dragView];
+ _tearOffStyle = tearOffStyle;
+
+ if (tearOffStyle == PSMTabBarTearOffMiniwindow) {
+ [window setBackgroundColor:[NSColor clearColor]];
+ [window setHasShadow:YES];
+ }
+
+ [window setAlphaValue:kPSMTabDragWindowAlpha];
+ }
+ return self;
+}
+
+- (void)dealloc
+{
+ [_timer invalidate];
+}
+
+- (NSImage *)image
+{
+ return [_view image];
+}
+
+- (NSImage *)alternateImage
+{
+ return [_view alternateImage];
+}
+
+- (void)setAlternateImage:(NSImage *)image
+{
+ [_view setAlternateImage:image];
+}
+
+- (BOOL)isAnimating
+{
+ return _animation != nil;
+}
+
+- (void)switchImages
+{
+ if (_tearOffStyle != PSMTabBarTearOffMiniwindow || ![_view alternateImage]) {
+ return;
+ }
+
+ CGFloat progress = 0;
+ _showingAlternate = !_showingAlternate;
+
+ if (_animation) {
+ //An animation already exists, get the current progress
+ progress = 1.0f - [_animation currentProgress];
+ [_animation stopAnimation];
+ }
+
+ //begin animating
+ _animation = [[NSAnimation alloc] initWithDuration:0.25 animationCurve:NSAnimationEaseInOut];
+ [_animation setAnimationBlockingMode:NSAnimationNonblocking];
+ [_animation setCurrentProgress:progress];
+ [_animation startAnimation];
+
+ _originalWindowFrame = [[self window] frame];
+
+ if (_timer) {
+ [_timer invalidate];
+ }
+ _timer = [NSTimer scheduledTimerWithTimeInterval:1.0f / 30.0f target:self selector:@selector(animateTimer:) userInfo:nil repeats:YES];
+}
+
+- (void)animateTimer:(NSTimer *)timer
+{
+ NSRect frame = _originalWindowFrame;
+ NSImage *currentImage = _showingAlternate ? [_view alternateImage] : [_view image];
+ NSSize size = [currentImage size];
+ NSPoint mousePoint = [NSEvent mouseLocation];
+ CGFloat animationValue = [_animation currentValue];
+
+ frame.size.width = _originalWindowFrame.size.width + (size.width - _originalWindowFrame.size.width) * animationValue;
+ frame.size.height = _originalWindowFrame.size.height + (size.height - _originalWindowFrame.size.height) * animationValue;
+ frame.origin.x = mousePoint.x - (frame.size.width / 2);
+ frame.origin.y = mousePoint.y - (frame.size.height / 2);
+
+ [_view setFadeValue:_showingAlternate ? 1.0f - animationValue : animationValue];
+ [[self window] setFrame:frame display:YES];
+
+ if (![_animation isAnimating]) {
+ _animation = nil;
+ [timer invalidate];
+ _timer = nil;
+ }
+}
+
+@end
diff --git a/frontends/cocoa/PSMTabBarControl/PSMTabStyle.h b/frontends/cocoa/PSMTabBarControl/PSMTabStyle.h
new file mode 100644
index 000000000..ca3717435
--- /dev/null
+++ b/frontends/cocoa/PSMTabBarControl/PSMTabStyle.h
@@ -0,0 +1,57 @@
+//
+// PSMTabStyle.h
+// PSMTabBarControl
+//
+// Created by John Pannell on 2/17/06.
+// Copyright 2006 Positive Spin Media. All rights reserved.
+//
+
+/*
+ Protocol to be observed by all style delegate objects. These objects handle the drawing responsibilities for PSMTabBarCell; once the control has been assigned a style, the background and cells draw consistent with that style. Design pattern and implementation by David Smith, Seth Willits, and Chris Forsythe, all touch up and errors by John P. :-)
+ */
+
+#import "PSMTabBarCell.h"
+#import "PSMTabBarControl.h"
+
+@protocol PSMTabStyle <NSObject>
+
+// identity
+- (NSString *)name;
+
+// control specific parameters
+- (CGFloat)leftMarginForTabBarControl;
+- (CGFloat)rightMarginForTabBarControl;
+- (CGFloat)topMarginForTabBarControl;
+- (void)setOrientation:(PSMTabBarOrientation)value;
+
+// add tab button
+- (NSImage *)addTabButtonImage;
+- (NSImage *)addTabButtonPressedImage;
+- (NSImage *)addTabButtonRolloverImage;
+
+// cell specific parameters
+- (NSRect)dragRectForTabCell:(PSMTabBarCell *)cell orientation:(PSMTabBarOrientation)orientation;
+- (NSRect)closeButtonRectForTabCell:(PSMTabBarCell *)cell withFrame:(NSRect)cellFrame;
+- (NSRect)iconRectForTabCell:(PSMTabBarCell *)cell;
+- (NSRect)indicatorRectForTabCell:(PSMTabBarCell *)cell;
+- (NSRect)objectCounterRectForTabCell:(PSMTabBarCell *)cell;
+- (CGFloat)minimumWidthOfTabCell:(PSMTabBarCell *)cell;
+- (CGFloat)desiredWidthOfTabCell:(PSMTabBarCell *)cell;
+- (CGFloat)tabCellHeight;
+
+// cell values
+- (NSAttributedString *)attributedObjectCountValueForTabCell:(PSMTabBarCell *)cell;
+- (NSAttributedString *)attributedStringValueForTabCell:(PSMTabBarCell *)cell;
+
+// drawing
+- (void)drawTabCell:(PSMTabBarCell *)cell;
+- (void)drawBackgroundInRect:(NSRect)rect;
+- (void)drawTabBar:(PSMTabBarControl *)bar inRect:(NSRect)rect;
+
+@end
+
+@interface PSMTabBarControl (StyleAccessors)
+
+- (NSMutableArray *)cells;
+
+@end
diff --git a/frontends/cocoa/PSMTabBarControl/PSMUnifiedTabStyle.h b/frontends/cocoa/PSMTabBarControl/PSMUnifiedTabStyle.h
new file mode 100644
index 000000000..319fd729d
--- /dev/null
+++ b/frontends/cocoa/PSMTabBarControl/PSMUnifiedTabStyle.h
@@ -0,0 +1,29 @@
+//
+// PSMUnifiedTabStyle.h
+// --------------------
+//
+// Created by Keith Blount on 30/04/2006.
+// Copyright 2006 __MyCompanyName__. All rights reserved.
+//
+
+#import <Cocoa/Cocoa.h>
+#import "PSMTabStyle.h"
+
+@interface PSMUnifiedTabStyle : NSObject <PSMTabStyle> {
+ NSImage *unifiedCloseButton;
+ NSImage *unifiedCloseButtonDown;
+ NSImage *unifiedCloseButtonOver;
+ NSImage *unifiedCloseDirtyButton;
+ NSImage *unifiedCloseDirtyButtonDown;
+ NSImage *unifiedCloseDirtyButtonOver;
+ NSImage *_addTabButtonImage;
+ NSImage *_addTabButtonPressedImage;
+ NSImage *_addTabButtonRolloverImage;
+
+ NSDictionary *_objectCountStringAttributes;
+
+ CGFloat leftMargin;
+ PSMTabBarControl *tabBar;
+}
+- (void)setLeftMarginForTabBarControl:(CGFloat)margin;
+@end
diff --git a/frontends/cocoa/PSMTabBarControl/PSMUnifiedTabStyle.m b/frontends/cocoa/PSMTabBarControl/PSMUnifiedTabStyle.m
new file mode 100644
index 000000000..687c7792c
--- /dev/null
+++ b/frontends/cocoa/PSMTabBarControl/PSMUnifiedTabStyle.m
@@ -0,0 +1,581 @@
+//
+// PSMUnifiedTabStyle.m
+// --------------------
+//
+// Created by Keith Blount on 30/04/2006.
+// Copyright 2006 __MyCompanyName__. All rights reserved.
+//
+
+#import "PSMUnifiedTabStyle.h"
+#import "PSMTabBarCell.h"
+#import "PSMTabBarControl.h"
+#import "NSBezierPath_AMShading.h"
+
+#define kPSMUnifiedObjectCounterRadius 7.0
+#define kPSMUnifiedCounterMinWidth 20
+
+@interface PSMUnifiedTabStyle (Private)
+- (void)drawInteriorWithTabCell:(PSMTabBarCell *)cell inView:(NSView *)controlView;
+@end
+
+@implementation PSMUnifiedTabStyle
+
+- (NSString *)name
+{
+ return @"Unified";
+}
+
+#pragma mark -
+#pragma mark Creation/Destruction
+
+- (id)init
+{
+ if ((self = [super init])) {
+ unifiedCloseButton = [[NSImage alloc] initByReferencingFile:[[PSMTabBarControl bundle] pathForImageResource:@"AquaTabClose_Front"]];
+ unifiedCloseButtonDown = [[NSImage alloc] initByReferencingFile:[[PSMTabBarControl bundle] pathForImageResource:@"AquaTabClose_Front_Pressed"]];
+ unifiedCloseButtonOver = [[NSImage alloc] initByReferencingFile:[[PSMTabBarControl bundle] pathForImageResource:@"AquaTabClose_Front_Rollover"]];
+
+ unifiedCloseDirtyButton = [[NSImage alloc] initByReferencingFile:[[PSMTabBarControl bundle] pathForImageResource:@"AquaTabCloseDirty_Front"]];
+ unifiedCloseDirtyButtonDown = [[NSImage alloc] initByReferencingFile:[[PSMTabBarControl bundle] pathForImageResource:@"AquaTabCloseDirty_Front_Pressed"]];
+ unifiedCloseDirtyButtonOver = [[NSImage alloc] initByReferencingFile:[[PSMTabBarControl bundle] pathForImageResource:@"AquaTabCloseDirty_Front_Rollover"]];
+
+ _addTabButtonImage = [[NSImage alloc] initByReferencingFile:[[PSMTabBarControl bundle] pathForImageResource:@"AquaTabNew"]];
+ _addTabButtonPressedImage = [[NSImage alloc] initByReferencingFile:[[PSMTabBarControl bundle] pathForImageResource:@"AquaTabNewPressed"]];
+ _addTabButtonRolloverImage = [[NSImage alloc] initByReferencingFile:[[PSMTabBarControl bundle] pathForImageResource:@"AquaTabNewRollover"]];
+
+ _objectCountStringAttributes = [[NSDictionary alloc] initWithObjectsAndKeys:[[NSFontManager sharedFontManager] convertFont:[NSFont fontWithName:@"Helvetica" size:11.0] toHaveTrait:NSBoldFontMask], NSFontAttributeName,
+ [[NSColor whiteColor] colorWithAlphaComponent:0.85], NSForegroundColorAttributeName,
+ nil, nil];
+
+ leftMargin = 5.0;
+ }
+ return self;
+}
+
+#pragma mark -
+#pragma mark Control Specific
+
+- (void)setLeftMarginForTabBarControl:(CGFloat)margin
+{
+ leftMargin = margin;
+}
+
+- (CGFloat)leftMarginForTabBarControl
+{
+ return leftMargin;
+}
+
+- (CGFloat)rightMarginForTabBarControl
+{
+ return 24.0f;
+}
+
+- (CGFloat)topMarginForTabBarControl
+{
+ return 10.0f;
+}
+
+- (void)setOrientation:(PSMTabBarOrientation)value
+{
+}
+
+#pragma mark -
+#pragma mark Add Tab Button
+
+- (NSImage *)addTabButtonImage
+{
+ return _addTabButtonImage;
+}
+
+- (NSImage *)addTabButtonPressedImage
+{
+ return _addTabButtonPressedImage;
+}
+
+- (NSImage *)addTabButtonRolloverImage
+{
+ return _addTabButtonRolloverImage;
+}
+
+#pragma mark -
+#pragma mark Cell Specific
+
+- (NSRect)dragRectForTabCell:(PSMTabBarCell *)cell orientation:(PSMTabBarOrientation)orientation
+{
+ NSRect dragRect = [cell frame];
+ dragRect.size.width++;
+ return dragRect;
+}
+
+- (NSRect)closeButtonRectForTabCell:(PSMTabBarCell *)cell withFrame:(NSRect)cellFrame
+{
+ if ([cell hasCloseButton] == NO) {
+ return NSZeroRect;
+ }
+
+ NSRect result;
+ result.size = [unifiedCloseButton size];
+ result.origin.x = cellFrame.origin.x + MARGIN_X;
+ result.origin.y = cellFrame.origin.y + MARGIN_Y + 1.0;
+
+ return result;
+}
+
+- (NSRect)iconRectForTabCell:(PSMTabBarCell *)cell
+{
+ NSRect cellFrame = [cell frame];
+
+ if ([cell hasIcon] == NO) {
+ return NSZeroRect;
+ }
+
+ NSRect result;
+ result.size = NSMakeSize(kPSMTabBarIconWidth, kPSMTabBarIconWidth);
+ result.origin.x = cellFrame.origin.x + MARGIN_X;
+ result.origin.y = cellFrame.origin.y + MARGIN_Y - 1.0;
+
+ if ([cell hasCloseButton] && ![cell isCloseButtonSuppressed]) {
+ result.origin.x += [unifiedCloseButton size].width + kPSMTabBarCellPadding;
+ }
+
+ return result;
+}
+
+- (NSRect)indicatorRectForTabCell:(PSMTabBarCell *)cell
+{
+ NSRect cellFrame = [cell frame];
+
+ if ([[cell indicator] isHidden]) {
+ return NSZeroRect;
+ }
+
+ NSRect result;
+ result.size = NSMakeSize(kPSMTabBarIndicatorWidth, kPSMTabBarIndicatorWidth);
+ result.origin.x = cellFrame.origin.x + cellFrame.size.width - MARGIN_X - kPSMTabBarIndicatorWidth;
+ result.origin.y = cellFrame.origin.y + MARGIN_Y - 1.0;
+
+ return result;
+}
+
+- (NSRect)objectCounterRectForTabCell:(PSMTabBarCell *)cell
+{
+ NSRect cellFrame = [cell frame];
+
+ if ([cell count] == 0) {
+ return NSZeroRect;
+ }
+
+ CGFloat countWidth = [[self attributedObjectCountValueForTabCell:cell] size].width;
+ countWidth += (2 * kPSMUnifiedObjectCounterRadius - 6.0);
+ if (countWidth < kPSMUnifiedCounterMinWidth) {
+ countWidth = kPSMUnifiedCounterMinWidth;
+ }
+
+ NSRect result;
+ result.size = NSMakeSize(countWidth, 2 * kPSMUnifiedObjectCounterRadius); // temp
+ result.origin.x = cellFrame.origin.x + cellFrame.size.width - MARGIN_X - result.size.width;
+ result.origin.y = cellFrame.origin.y + MARGIN_Y + 1.0;
+
+ if (![[cell indicator] isHidden]) {
+ result.origin.x -= kPSMTabBarIndicatorWidth + kPSMTabBarCellPadding;
+ }
+
+ return result;
+}
+
+- (CGFloat)minimumWidthOfTabCell:(PSMTabBarCell *)cell
+{
+ CGFloat resultWidth = 0.0;
+
+ // left margin
+ resultWidth = MARGIN_X;
+
+ // close button?
+ if ([cell hasCloseButton] && ![cell isCloseButtonSuppressed]) {
+ resultWidth += [unifiedCloseButton size].width + kPSMTabBarCellPadding;
+ }
+
+ // icon?
+ if ([cell hasIcon]) {
+ resultWidth += kPSMTabBarIconWidth + kPSMTabBarCellPadding;
+ }
+
+ // the label
+ resultWidth += kPSMMinimumTitleWidth;
+
+ // object counter?
+ if ([cell count] > 0) {
+ resultWidth += [self objectCounterRectForTabCell:cell].size.width + kPSMTabBarCellPadding;
+ }
+
+ // indicator?
+ if ([[cell indicator] isHidden] == NO) {
+ resultWidth += kPSMTabBarCellPadding + kPSMTabBarIndicatorWidth;
+ }
+
+ // right margin
+ resultWidth += MARGIN_X;
+
+ return ceil(resultWidth);
+}
+
+- (CGFloat)desiredWidthOfTabCell:(PSMTabBarCell *)cell
+{
+ CGFloat resultWidth = 0.0;
+
+ // left margin
+ resultWidth = MARGIN_X;
+
+ // close button?
+ if ([cell hasCloseButton] && ![cell isCloseButtonSuppressed]) {
+ resultWidth += [unifiedCloseButton size].width + kPSMTabBarCellPadding;
+ }
+
+ // icon?
+ if ([cell hasIcon]) {
+ resultWidth += kPSMTabBarIconWidth + kPSMTabBarCellPadding;
+ }
+
+ // the label
+ resultWidth += [[cell attributedStringValue] size].width;
+
+ // object counter?
+ if ([cell count] > 0) {
+ resultWidth += [self objectCounterRectForTabCell:cell].size.width + kPSMTabBarCellPadding;
+ }
+
+ // indicator?
+ if ([[cell indicator] isHidden] == NO) {
+ resultWidth += kPSMTabBarCellPadding + kPSMTabBarIndicatorWidth;
+ }
+
+ // right margin
+ resultWidth += MARGIN_X;
+
+ return ceil(resultWidth);
+}
+
+- (CGFloat)tabCellHeight
+{
+ return kPSMTabBarControlHeight;
+}
+
+#pragma mark -
+#pragma mark Cell Values
+
+- (NSAttributedString *)attributedObjectCountValueForTabCell:(PSMTabBarCell *)cell
+{
+ NSString *contents = [NSString stringWithFormat:@"%lu", (unsigned long)[cell count]];
+ return [[NSMutableAttributedString alloc] initWithString:contents attributes:_objectCountStringAttributes];
+}
+
+- (NSAttributedString *)attributedStringValueForTabCell:(PSMTabBarCell *)cell
+{
+ NSMutableAttributedString *attrStr;
+ NSString *contents = [cell stringValue];
+ attrStr = [[NSMutableAttributedString alloc] initWithString:contents];
+ NSRange range = NSMakeRange(0, [contents length]);
+
+ [attrStr addAttribute:NSFontAttributeName value:[NSFont systemFontOfSize:11.0] range:range];
+
+ // Paragraph Style for Truncating Long Text
+ static NSMutableParagraphStyle *TruncatingTailParagraphStyle = nil;
+ if (!TruncatingTailParagraphStyle) {
+ TruncatingTailParagraphStyle = [[NSParagraphStyle defaultParagraphStyle] mutableCopy];
+ [TruncatingTailParagraphStyle setLineBreakMode:NSLineBreakByTruncatingTail];
+ }
+ [attrStr addAttribute:NSParagraphStyleAttributeName value:TruncatingTailParagraphStyle range:range];
+
+ return attrStr;
+}
+
+#pragma mark -
+#pragma mark---- drawing ----
+
+- (void)drawTabCell:(PSMTabBarCell *)cell
+{
+ NSRect cellFrame = [cell frame];
+
+ NSToolbar *toolbar = [[[cell controlView] window] toolbar];
+ BOOL showsBaselineSeparator = (toolbar && [toolbar respondsToSelector:@selector(showsBaselineSeparator)] && [toolbar showsBaselineSeparator]);
+ if (!showsBaselineSeparator) {
+ cellFrame.origin.y += 1.0;
+ cellFrame.size.height -= 1.0;
+ }
+
+ NSColor *lineColor = nil;
+ NSBezierPath *bezier = [NSBezierPath bezierPath];
+ lineColor = [NSColor colorWithCalibratedWhite:0.576 alpha:1.0];
+
+ if (!showsBaselineSeparator || [cell state] == NSOnState) {
+ // selected tab
+ NSRect aRect = NSMakeRect(cellFrame.origin.x + 0.5, cellFrame.origin.y - 0.5, cellFrame.size.width, cellFrame.size.height);
+
+ // frame
+ CGFloat radius = MIN(6.0, 0.5f * MIN(NSWidth(aRect), NSHeight(aRect)));
+ NSRect rect = NSInsetRect(aRect, radius, radius);
+
+ [bezier appendBezierPathWithArcWithCenter:NSMakePoint(NSMinX(rect), NSMinY(rect)) radius:radius startAngle:180.0 endAngle:270.0];
+
+ [bezier appendBezierPathWithArcWithCenter:NSMakePoint(NSMaxX(rect), NSMinY(rect)) radius:radius startAngle:270.0 endAngle:360.0];
+
+ NSPoint cornerPoint = NSMakePoint(NSMaxX(aRect), NSMaxY(aRect));
+ [bezier appendBezierPathWithPoints:&cornerPoint count:1];
+
+ cornerPoint = NSMakePoint(NSMinX(aRect), NSMaxY(aRect));
+ [bezier appendBezierPathWithPoints:&cornerPoint count:1];
+
+ [bezier closePath];
+
+ //[[NSColor windowBackgroundColor] set];
+ //[bezier fill];
+ if ([NSApp isActive]) {
+ if ([cell state] == NSOnState) {
+ [bezier linearGradientFillWithStartColor:[NSColor colorWithCalibratedWhite:0.99 alpha:1.0]
+ endColor:[NSColor colorWithCalibratedWhite:0.941 alpha:1.0]];
+ } else if ([cell isHighlighted]) {
+ [bezier linearGradientFillWithStartColor:[NSColor colorWithCalibratedWhite:0.80 alpha:1.0]
+ endColor:[NSColor colorWithCalibratedWhite:0.80 alpha:1.0]];
+ } else {
+ [bezier linearGradientFillWithStartColor:[NSColor colorWithCalibratedWhite:0.835 alpha:1.0]
+ endColor:[NSColor colorWithCalibratedWhite:0.843 alpha:1.0]];
+ }
+ }
+
+ [lineColor set];
+ [bezier stroke];
+ } else {
+ // unselected tab
+ NSRect aRect = NSMakeRect(cellFrame.origin.x, cellFrame.origin.y, cellFrame.size.width, cellFrame.size.height);
+ aRect.origin.y += 0.5;
+ aRect.origin.x += 1.5;
+ aRect.size.width -= 1;
+
+ aRect.origin.x -= 1;
+ aRect.size.width += 1;
+
+ // rollover
+ if ([cell isHighlighted]) {
+ [[NSColor colorWithCalibratedWhite:0.0 alpha:0.1] set];
+ NSRectFillUsingOperation(aRect, NSCompositingOperationSourceAtop);
+ }
+
+ // frame
+
+ [lineColor set];
+ [bezier moveToPoint:NSMakePoint(aRect.origin.x + aRect.size.width, aRect.origin.y - 0.5)];
+ if (!([cell tabState] & PSMTab_RightIsSelectedMask)) {
+ [bezier lineToPoint:NSMakePoint(NSMaxX(aRect), NSMaxY(aRect))];
+ }
+
+ [bezier stroke];
+
+ // Create a thin lighter line next to the dividing line for a bezel effect
+ if (!([cell tabState] & PSMTab_RightIsSelectedMask)) {
+ [[[NSColor whiteColor] colorWithAlphaComponent:0.5] set];
+ [NSBezierPath strokeLineFromPoint:NSMakePoint(NSMaxX(aRect) + 1.0, aRect.origin.y - 0.5)
+ toPoint:NSMakePoint(NSMaxX(aRect) + 1.0, NSMaxY(aRect) - 2.5)];
+ }
+
+ // If this is the leftmost tab, we want to draw a line on the left, too
+ if ([cell tabState] & PSMTab_PositionLeftMask) {
+ [lineColor set];
+ [NSBezierPath strokeLineFromPoint:NSMakePoint(aRect.origin.x, aRect.origin.y - 0.5)
+ toPoint:NSMakePoint(aRect.origin.x, NSMaxY(aRect) - 2.5)];
+ [[[NSColor whiteColor] colorWithAlphaComponent:0.5] set];
+ [NSBezierPath strokeLineFromPoint:NSMakePoint(aRect.origin.x + 1.0, aRect.origin.y - 0.5)
+ toPoint:NSMakePoint(aRect.origin.x + 1.0, NSMaxY(aRect) - 2.5)];
+ }
+ }
+
+ [self drawInteriorWithTabCell:cell inView:[cell controlView]];
+}
+
+- (void)drawInteriorWithTabCell:(PSMTabBarCell *)cell inView:(NSView *)controlView
+{
+ NSRect cellFrame = [cell frame];
+ CGFloat labelPosition = cellFrame.origin.x + MARGIN_X;
+
+ // close button
+ if ([cell hasCloseButton] && ![cell isCloseButtonSuppressed]) {
+ NSSize closeButtonSize = NSZeroSize;
+ NSRect closeButtonRect = [cell closeButtonRectForFrame:cellFrame];
+ NSImage *closeButton = nil;
+
+ closeButton = [cell isEdited] ? unifiedCloseDirtyButton : unifiedCloseButton;
+
+ if ([cell closeButtonOver]) {
+ closeButton = [cell isEdited] ? unifiedCloseDirtyButtonOver : unifiedCloseButtonOver;
+ }
+ if ([cell closeButtonPressed]) {
+ closeButton = [cell isEdited] ? unifiedCloseDirtyButtonDown : unifiedCloseButtonDown;
+ }
+
+ closeButtonSize = [closeButton size];
+ if ([controlView isFlipped]) {
+ closeButtonRect.origin.y += closeButtonRect.size.height;
+ }
+
+ [closeButton drawAtPoint:closeButtonRect.origin fromRect:CGRectZero operation:NSCompositingOperationSourceOver fraction:1.0];
+
+ // scoot label over
+ labelPosition += closeButtonSize.width + kPSMTabBarCellPadding;
+ }
+
+ // icon
+ if ([cell hasIcon]) {
+ NSRect iconRect = [self iconRectForTabCell:cell];
+ NSImage *icon = [[[cell representedObject] identifier] icon];
+ if ([controlView isFlipped]) {
+ iconRect.origin.y += iconRect.size.height;
+ }
+
+ // center in available space (in case icon image is smaller than kPSMTabBarIconWidth)
+ if ([icon size].width < kPSMTabBarIconWidth) {
+ iconRect.origin.x += (kPSMTabBarIconWidth - [icon size].width) / 2.0;
+ }
+ if ([icon size].height < kPSMTabBarIconWidth) {
+ iconRect.origin.y -= (kPSMTabBarIconWidth - [icon size].height) / 2.0;
+ }
+
+ [icon drawAtPoint:iconRect.origin fromRect:NSZeroRect operation:NSCompositingOperationSourceOver fraction:1.0];
+
+ // scoot label over
+ labelPosition += iconRect.size.width + kPSMTabBarCellPadding;
+ }
+
+ // label rect
+ NSRect labelRect;
+ labelRect.origin.x = labelPosition;
+ labelRect.size.width = cellFrame.size.width - (labelRect.origin.x - cellFrame.origin.x) - kPSMTabBarCellPadding;
+ NSSize s = [[cell attributedStringValue] size];
+ labelRect.origin.y = cellFrame.origin.y + (cellFrame.size.height - s.height) / 2.0 - 1.0;
+ labelRect.size.height = s.height;
+
+ if (![[cell indicator] isHidden]) {
+ labelRect.size.width -= (kPSMTabBarIndicatorWidth + kPSMTabBarCellPadding);
+ }
+
+ // object counter
+ if ([cell count] > 0) {
+ [[cell countColor] ?: [NSColor colorWithCalibratedWhite:0.3 alpha:0.6] set];
+ NSBezierPath *path = [NSBezierPath bezierPath];
+ NSRect myRect = [self objectCounterRectForTabCell:cell];
+ myRect.origin.y -= 1.0;
+ [path moveToPoint:NSMakePoint(myRect.origin.x + kPSMUnifiedObjectCounterRadius, myRect.origin.y)];
+ [path lineToPoint:NSMakePoint(myRect.origin.x + myRect.size.width - kPSMUnifiedObjectCounterRadius, myRect.origin.y)];
+ [path appendBezierPathWithArcWithCenter:NSMakePoint(myRect.origin.x + myRect.size.width - kPSMUnifiedObjectCounterRadius, myRect.origin.y + kPSMUnifiedObjectCounterRadius) radius:kPSMUnifiedObjectCounterRadius startAngle:270.0 endAngle:90.0];
+ [path lineToPoint:NSMakePoint(myRect.origin.x + kPSMUnifiedObjectCounterRadius, myRect.origin.y + myRect.size.height)];
+ [path appendBezierPathWithArcWithCenter:NSMakePoint(myRect.origin.x + kPSMUnifiedObjectCounterRadius, myRect.origin.y + kPSMUnifiedObjectCounterRadius) radius:kPSMUnifiedObjectCounterRadius startAngle:90.0 endAngle:270.0];
+ [path fill];
+
+ // draw attributed string centered in area
+ NSRect counterStringRect;
+ NSAttributedString *counterString = [self attributedObjectCountValueForTabCell:cell];
+ counterStringRect.size = [counterString size];
+ counterStringRect.origin.x = myRect.origin.x + ((myRect.size.width - counterStringRect.size.width) / 2.0) + 0.25;
+ counterStringRect.origin.y = myRect.origin.y + ((myRect.size.height - counterStringRect.size.height) / 2.0) + 0.5;
+ [counterString drawInRect:counterStringRect];
+
+ labelRect.size.width -= myRect.size.width + kPSMTabBarCellPadding;
+ }
+
+ // label
+ [[cell attributedStringValue] drawInRect:labelRect];
+}
+
+- (void)drawBackgroundInRect:(NSRect)rect
+{
+ //Draw for our whole bounds; it'll be automatically clipped to fit the appropriate drawing area
+ rect = [tabBar bounds];
+
+ NSRect gradientRect = rect;
+ gradientRect.size.height -= 1.0;
+
+ NSBezierPath *path = [NSBezierPath bezierPathWithRect:gradientRect];
+ [path linearGradientFillWithStartColor:[NSColor colorWithCalibratedWhite:0.835 alpha:1.0]
+ endColor:[NSColor colorWithCalibratedWhite:0.843 alpha:1.0]];
+ [[NSColor colorWithCalibratedWhite:0.576 alpha:1.0] set];
+ [NSBezierPath strokeLineFromPoint:NSMakePoint(rect.origin.x, NSMaxY(rect) - 0.5)
+ toPoint:NSMakePoint(NSMaxX(rect), NSMaxY(rect) - 0.5)];
+
+ if (![[[tabBar tabView] window] isKeyWindow]) {
+ [[NSColor windowBackgroundColor] set];
+ NSRectFill(gradientRect);
+ }
+}
+
+- (void)drawTabBar:(PSMTabBarControl *)bar inRect:(NSRect)rect
+{
+ tabBar = bar;
+ [self drawBackgroundInRect:rect];
+
+ // no tab view == not connected
+ if (![bar tabView]) {
+ NSRect labelRect = rect;
+ labelRect.size.height -= 4.0;
+ labelRect.origin.y += 4.0;
+ NSMutableAttributedString *attrStr;
+ NSString *contents = @"PSMTabBarControl";
+ attrStr = [[NSMutableAttributedString alloc] initWithString:contents];
+ NSRange range = NSMakeRange(0, [contents length]);
+ [attrStr addAttribute:NSFontAttributeName value:[NSFont systemFontOfSize:11.0] range:range];
+ NSMutableParagraphStyle *centeredParagraphStyle = nil;
+ if (!centeredParagraphStyle) {
+ centeredParagraphStyle = [[NSParagraphStyle defaultParagraphStyle] mutableCopy];
+ [centeredParagraphStyle setAlignment:NSTextAlignmentCenter];
+ }
+ [attrStr addAttribute:NSParagraphStyleAttributeName value:centeredParagraphStyle range:range];
+ [attrStr drawInRect:labelRect];
+ return;
+ }
+
+ // draw cells
+ NSEnumerator *e = [[bar cells] objectEnumerator];
+ PSMTabBarCell *cell;
+ while ((cell = [e nextObject])) {
+ if ([bar isAnimating] || (![cell isInOverflowMenu] && NSIntersectsRect([cell frame], rect))) {
+ [cell drawWithFrame:[cell frame] inView:bar];
+ }
+ }
+}
+
+#pragma mark -
+#pragma mark Archiving
+
+- (void)encodeWithCoder:(NSCoder *)aCoder
+{
+ //[super encodeWithCoder:aCoder];
+ if ([aCoder allowsKeyedCoding]) {
+ [aCoder encodeObject:unifiedCloseButton forKey:@"unifiedCloseButton"];
+ [aCoder encodeObject:unifiedCloseButtonDown forKey:@"unifiedCloseButtonDown"];
+ [aCoder encodeObject:unifiedCloseButtonOver forKey:@"unifiedCloseButtonOver"];
+ [aCoder encodeObject:unifiedCloseDirtyButton forKey:@"unifiedCloseDirtyButton"];
+ [aCoder encodeObject:unifiedCloseDirtyButtonDown forKey:@"unifiedCloseDirtyButtonDown"];
+ [aCoder encodeObject:unifiedCloseDirtyButtonOver forKey:@"unifiedCloseDirtyButtonOver"];
+ [aCoder encodeObject:_addTabButtonImage forKey:@"addTabButtonImage"];
+ [aCoder encodeObject:_addTabButtonPressedImage forKey:@"addTabButtonPressedImage"];
+ [aCoder encodeObject:_addTabButtonRolloverImage forKey:@"addTabButtonRolloverImage"];
+ }
+}
+
+- (id)initWithCoder:(NSCoder *)aDecoder
+{
+ // self = [super initWithCoder:aDecoder];
+ //if (self) {
+ if ([aDecoder allowsKeyedCoding]) {
+ unifiedCloseButton = [aDecoder decodeObjectForKey:@"unifiedCloseButton"];
+ unifiedCloseButtonDown = [aDecoder decodeObjectForKey:@"unifiedCloseButtonDown"];
+ unifiedCloseButtonOver = [aDecoder decodeObjectForKey:@"unifiedCloseButtonOver"];
+ unifiedCloseDirtyButton = [aDecoder decodeObjectForKey:@"unifiedCloseDirtyButton"];
+ unifiedCloseDirtyButtonDown = [aDecoder decodeObjectForKey:@"unifiedCloseDirtyButtonDown"];
+ unifiedCloseDirtyButtonOver = [aDecoder decodeObjectForKey:@"unifiedCloseDirtyButtonOver"];
+ _addTabButtonImage = [aDecoder decodeObjectForKey:@"addTabButtonImage"];
+ _addTabButtonPressedImage = [aDecoder decodeObjectForKey:@"addTabButtonPressedImage"];
+ _addTabButtonRolloverImage = [aDecoder decodeObjectForKey:@"addTabButtonRolloverImage"];
+ }
+ //}
+ return self;
+}
+
+@end
diff --git a/frontends/cocoa/PSMTabBarControl/ReadMe.rtfd/TXT.rtf b/frontends/cocoa/PSMTabBarControl/ReadMe.rtfd/TXT.rtf
new file mode 100644
index 000000000..acd9372a2
--- /dev/null
+++ b/frontends/cocoa/PSMTabBarControl/ReadMe.rtfd/TXT.rtf
@@ -0,0 +1,186 @@
+{\rtf1\mac\ansicpg10000\cocoartf824\cocoasubrtf380
+{\fonttbl\f0\fswiss\fcharset77 Helvetica-Bold;\f1\fswiss\fcharset77 Helvetica;\f2\fswiss\fcharset77 Helvetica-Oblique;
+\f3\fnil\fcharset77 Monaco;}
+{\colortbl;\red255\green255\blue255;\red118\green15\blue80;\red0\green0\blue255;\red35\green110\blue37;
+}
+{\*\listtable{\list\listtemplateid1\listhybrid{\listlevel\levelnfc23\levelnfcn23\leveljc2\leveljcn2\levelfollow0\levelstartat1\levelspace360\levelindent0{\*\levelmarker \{disc\}}{\leveltext\leveltemplateid0\'02\'05.;}{\levelnumbers\'01;}}{\listname ;}\listid1}}
+{\*\listoverridetable{\listoverride\listid1\listoverridecount0\ls1}}
+\pard\tx720\tx1440\tx2160\tx2880\tx3600\tx4320\tx5040\tx5760\tx6480\tx7200\tx7920\tx8640\qc\pardirnatural
+
+\f0\b\fs24 \cf0 \
+PSMTabBarControl (and related classes)\
+
+\f1\b0 developed by John Pannell, Positive Spin Media\
+\
+as seen in the super-cool app...\
+\pard\tx720\tx1440\tx2160\tx2880\tx3600\tx4320\tx5040\tx5760\tx6480\tx7200\tx7920\tx8640\qc\pardirnatural
+\cf0 {{\NeXTGraphic startpage.gif \width7200 \height2820
+}}\pard\tx720\tx1440\tx2160\tx2880\tx3600\tx4320\tx5040\tx5760\tx6480\tx7200\tx7920\tx8640\qc\pardirnatural
+\cf0 \
+\
+\pard\tx720\tx1440\tx2160\tx2880\tx3600\tx4320\tx5040\tx5760\tx6480\tx7200\tx7920\tx8640\ql\qnatural\pardirnatural
+\cf0 This source code and all related materials are released under the BSD license, which is explained at the end of this document, along with some other legalese. I've made my best effort to make everything bug free, but please let me know of any bugs found or suggestions you have: johnp@positivespinmedia.com.\
+\
+
+\f0\b Purpose
+\f1\b0 \
+\
+PSMTabBarControl seeks to provide developers with a high-quality, easy to use GUI to manage an NSTabView (or subclasses) in a manner similar to Safari's tabbed browsing implementation. It attempts to add a few features as well. Here's what you get:\
+\
+
+\f0\b The look:
+\f1\b0 a control/cell architecture that draws the expected tab appearance below a toolbar or similar view. Included styles work consistently in Aqua, Metal, or customized metal variations by basing fills on the window's background color. Includes drawing of a close button, and rollover states for the close button and tab cell. Also provides pop-up button and menu when tabs overflow available space, and support for individual tab progress indicators, icons, and object counters. Tabs can be drawn sized to fit the string content of the label, or uniformly sized.\
+\
+
+\f0\b The functionality:
+\f1\b0 Close button removes tabs, click on a tab cell selects. Indicators start, stop, and hide if things are hooked up correctly.\
+\
+
+\f0\b Extras:
+\f1\b0 Supports multi-window drag-and-drop reordering of the tabs with aqua-licious animation.\
+\
+
+\f0\b Files
+\f1\b0 \
+\
+Your project will need the files in the "Framework" folder of the project. The actual framework packages these (and some images) up nicely for you, if desired. Please look over the "TabBarControlDemo" target of the source code project to see exactly what is needed to get everything to build. Building and playing with the demo is also a good way to get a feel for the features provided by these classes.\
+\
+
+\f0\b Usage
+\f1\b0 \
+\
+Simply drag a custom view object from the views palette in IB, read the PSMTabBarControl class into IB, and set the view's custom class to PSMTabBarControl. Then connect the control's tabview outlet to the tab view being controlled, and make the control the delegate of the tab view. You can also connect the control's "partner view" outlet to another view that will resize in response to the hide/show behavior of the control.\
+\
+Alternately, you can build the Palette subproject and add the built IB palette to Interface Builder. In this case, creating and configuring an instance is as easy and drag, drop, and a few clicks. A demo movie and the built palette are available in a separate download from my website: http://www.positivespinmedia.com/dev/PSMTabBarControl.html\
+\
+
+\f2\i Please read the PSMTabBarControlDoc.html file in the documentation folder of this project. It provides an Apple-ish page describing the interface and usage of this object.
+\f1\i0 \
+\
+
+\f0\b Patterns of Use
+\f1\b0 \
+\
+There are a few random notes I can think of for usage guidelines...\
+\
+- You may see a line between the toolbar and the control in your app; it is part of the toolbar. In Tiger, you can eliminate the appearance of this line:\
+\
+\pard\tx960\tx1920\tx2880\tx3840\tx4800\tx5760\tx6720\tx7680\tx8640\tx9600\tx10560\tx11520\tx12480\tx13440\tx14400\tx15360\tx16320\tx17280\tx18240\tx19200\tx20160\tx21120\tx22080\tx23040\tx24000\tx24960\tx25920\tx26880\tx27840\tx28800\tx29760\tx30720\tx31680\tx32640\tx33600\tx34560\tx35520\tx36480\tx37440\tx38400\tx39360\tx40320\tx41280\tx42240\tx43200\tx44160\tx45120\tx46080\tx47040\tx48000\tx48960\tx49920\tx50880\tx51840\tx52800\tx53760\tx54720\tx55680\tx56640\tx57600\tx58560\tx59520\tx60480\tx61440\tx62400\tx63360\tx64320\tx65280\tx66240\tx67200\tx68160\tx69120\tx70080\tx71040\tx72000\tx72960\tx73920\tx74880\tx75840\tx76800\tx77760\tx78720\tx79680\tx80640\tx81600\tx82560\tx83520\tx84480\tx85440\tx86400\tx87360\tx88320\tx89280\tx90240\tx91200\tx92160\tx93120\tx94080\tx95040\tx96000\ql\qnatural\pardirnatural
+
+\f3\fs20 \cf0 \CocoaLigature0 SInt32 MacVersion;\
+\pard\tx960\tx1920\tx2880\tx3840\tx4800\tx5760\tx6720\tx7680\tx8640\tx9600\tx10560\tx11520\tx12480\tx13440\tx14400\tx15360\tx16320\tx17280\tx18240\tx19200\tx20160\tx21120\tx22080\tx23040\tx24000\tx24960\tx25920\tx26880\tx27840\tx28800\tx29760\tx30720\tx31680\tx32640\tx33600\tx34560\tx35520\tx36480\tx37440\tx38400\tx39360\tx40320\tx41280\tx42240\tx43200\tx44160\tx45120\tx46080\tx47040\tx48000\tx48960\tx49920\tx50880\tx51840\tx52800\tx53760\tx54720\tx55680\tx56640\tx57600\tx58560\tx59520\tx60480\tx61440\tx62400\tx63360\tx64320\tx65280\tx66240\tx67200\tx68160\tx69120\tx70080\tx71040\tx72000\tx72960\tx73920\tx74880\tx75840\tx76800\tx77760\tx78720\tx79680\tx80640\tx81600\tx82560\tx83520\tx84480\tx85440\tx86400\tx87360\tx88320\tx89280\tx90240\tx91200\tx92160\tx93120\tx94080\tx95040\tx96000\ql\qnatural\pardirnatural
+\cf2 if\cf0 (Gestalt(gestaltSystemVersion, &MacVersion) == noErr)\{\
+ \cf2 if\cf0 (MacVersion >= \cf3 0x1040\cf0 )\{\
+ \cf4 // this call is Tiger only\cf0 \
+ [toolbar setShowsBaselineSeparator:\cf2 NO\cf0 ];\
+ \}\
+\}\
+\
+\pard\tx960\tx1920\tx2880\tx3840\tx4800\tx5760\tx6720\tx7680\tx8640\tx9600\tx10560\tx11520\tx12480\tx13440\tx14400\tx15360\tx16320\tx17280\tx18240\tx19200\tx20160\tx21120\tx22080\tx23040\tx24000\tx24960\tx25920\tx26880\tx27840\tx28800\tx29760\tx30720\tx31680\tx32640\tx33600\tx34560\tx35520\tx36480\tx37440\tx38400\tx39360\tx40320\tx41280\tx42240\tx43200\tx44160\tx45120\tx46080\tx47040\tx48000\tx48960\tx49920\tx50880\tx51840\tx52800\tx53760\tx54720\tx55680\tx56640\tx57600\tx58560\tx59520\tx60480\tx61440\tx62400\tx63360\tx64320\tx65280\tx66240\tx67200\tx68160\tx69120\tx70080\tx71040\tx72000\tx72960\tx73920\tx74880\tx75840\tx76800\tx77760\tx78720\tx79680\tx80640\tx81600\tx82560\tx83520\tx84480\tx85440\tx86400\tx87360\tx88320\tx89280\tx90240\tx91200\tx92160\tx93120\tx94080\tx95040\tx96000\ql\qnatural\pardirnatural
+
+\f1\fs24 \cf0 - In general, there is no reason for your app objects to communicate (outside of configuration) with the PSMTabBarControl at all. Changes made to the NSTabView instance programmatically should be directed at the NSTabView instance itself, and the control will update to reflect the changes made.\
+\
+- Your app might want to receive tab view delegate notifications in order to perform some actions. No problem, simply make the desired object the delegate of the PSMTabBarControl instance... it passes along all tab view notifications. Note that it uses these notifications to make changes itself - read the source code to make sure you aren't tripping over something.\
+\
+- The control creates bindings between each cell's progress indicator and the represented NSTabViewItem's identifier object, if it can. In my app design, I set an instance of NSObjectController as the NSTabViewItem's identifier, and then bind to the "isProcessing" key of the controller's content object. All of this can be seen in the source of the demo app...\
+\
+- The control can be set to hide itself when there is only a single tab, and can also be told to hide/show on demand. It can animate to appear and disappear, and will resize something to compensate for the missing window real estate. By default, it will resize the window, but you can also connect the "partnerView" outlet in IB to specify another view to resize to take up the missing space. Note that this takes some attention to sizing springs and wires to get right, and complex views may need a container view to achieve the desired effect.\
+\
+- The control can be configured to draw an attractive "Add Tab" button at the end of the tab cells. Unfortunately, the button is all looks and no brains - it has no idea what your app wants to do when adding a tab. If you configure your app to show the add tab button, you need to hook up the add tab button to the proper target with the proper selector. Something like this will do nicely in your app controller's awakeFromNib:\
+\
+\pard\tx960\tx1920\tx2880\tx3840\tx4800\tx5760\tx6720\tx7680\tx8640\tx9600\tx10560\tx11520\tx12480\tx13440\tx14400\tx15360\tx16320\tx17280\tx18240\tx19200\tx20160\tx21120\tx22080\tx23040\tx24000\tx24960\tx25920\tx26880\tx27840\tx28800\tx29760\tx30720\tx31680\tx32640\tx33600\tx34560\tx35520\tx36480\tx37440\tx38400\tx39360\tx40320\tx41280\tx42240\tx43200\tx44160\tx45120\tx46080\tx47040\tx48000\tx48960\tx49920\tx50880\tx51840\tx52800\tx53760\tx54720\tx55680\tx56640\tx57600\tx58560\tx59520\tx60480\tx61440\tx62400\tx63360\tx64320\tx65280\tx66240\tx67200\tx68160\tx69120\tx70080\tx71040\tx72000\tx72960\tx73920\tx74880\tx75840\tx76800\tx77760\tx78720\tx79680\tx80640\tx81600\tx82560\tx83520\tx84480\tx85440\tx86400\tx87360\tx88320\tx89280\tx90240\tx91200\tx92160\tx93120\tx94080\tx95040\tx96000\ql\qnatural\pardirnatural
+
+\f3\fs20 \cf4 // hook up add tab button\cf0 \
+[[tabBar addTabButton] setTarget:\cf2 self\cf0 ];\
+[[tabBar addTabButton] setAction:\cf2 @selector\cf0 (addNewTab:)];
+\f1\fs24 \
+\
+- The tabs have some sizing options: You can specify the minimum width, maximum width, and optimum width, as well as spcifying if the tabs should size to fit their label or not. The sizing bahavior of the tabs is as follows: If "size to fit" is specified, then tabs will be generated to fit the label, but will never exceed the specified max or min widths. Once the end of the control is reached, the overflow menu will appear as tabs are added; the last tab will squeeze in if it can, or the remaining tabs will stretch to occupy the full control. If "size to fit" is not specified, then all successive tabs will appear at the optimum width. Once the end of the control is reached, adding new tabs will cause all tabs to shrink to accomodate, until the minumum width is reached, and then the overflow menu will be used; max width is ignored in this case. Hopefully that all makes sense :-)\
+\
+- PSMTabBarControl will load the existing tabs from the tabView outlet at startup. However, many of the advanced features (icon display, progress indicator, object count) rely on binding to a controller that is likely not set up in IB. Solution? Nuke the existing tabs in the NSTabView and add new ones, configured the way you like. The demo app does this in the awakeFromNib: method of the app controller.\
+\
+- As a design choice, I elected to keep a cell object around until its tab was closed, instead of "churning" cell objects in each update cycle. Each cell keeps its NSTabViewItem as its representedObject and maintains reference that way, rather than by any index. As a result of this, drag-and-drop reordering of tabs does not change the underlying NSTabView instance at all. All that to say: don't rely on numerical indices if communicating with both the control and the tab view - the indices may not correlate if the user moved some tabs around (and remember - you shouldn't need to communicate with the control anyway :-). The Shiira Project, from which I gained much insight and inspiration from for this UI element, elected to scrap and rebuild the array of cells each time through the update cycle, and rely on indices to correlate between cells and NSTabViewItems. I felt the representedObject route was cleaner, and preferred not to churn objects.\
+\
+\pard\tx960\tx1920\tx2880\tx3840\tx4800\tx5760\tx6720\tx7680\tx8640\tx9600\tx10560\tx11520\tx12480\tx13440\tx14400\tx15360\tx16320\tx17280\tx18240\tx19200\tx20160\tx21120\tx22080\tx23040\tx24000\tx24960\tx25920\tx26880\tx27840\tx28800\tx29760\tx30720\tx31680\tx32640\tx33600\tx34560\tx35520\tx36480\tx37440\tx38400\tx39360\tx40320\tx41280\tx42240\tx43200\tx44160\tx45120\tx46080\tx47040\tx48000\tx48960\tx49920\tx50880\tx51840\tx52800\tx53760\tx54720\tx55680\tx56640\tx57600\tx58560\tx59520\tx60480\tx61440\tx62400\tx63360\tx64320\tx65280\tx66240\tx67200\tx68160\tx69120\tx70080\tx71040\tx72000\tx72960\tx73920\tx74880\tx75840\tx76800\tx77760\tx78720\tx79680\tx80640\tx81600\tx82560\tx83520\tx84480\tx85440\tx86400\tx87360\tx88320\tx89280\tx90240\tx91200\tx92160\tx93120\tx94080\tx95040\tx96000\ql\qnatural\pardirnatural
+
+\f0\b \cf0 Improvements?
+\f1\b0 \
+\
+Pipe up if you think of something you'd like to see; here's my current list:\
+\
+- "Pop-up" tabs - like pop-up folders in the finder, in case you want to drag to a destination in another tab.\
+- Support for the
+\f3\fs22 \CocoaLigature1 NSUnifiedTitleAndToolbarWindowMask
+\f1\fs24 \CocoaLigature0 "unified" window appearance. (Help! I really searched around to try to make this work... the color pattern of the title and toolbar seem to be top secret! The new "unified" style is an excellent replication of a unified look, but isn't "built from" the unified appearance like the metal is.)\
+- During multi-window drag, having a "drag window/image" that shows the represented view getting moved to the other window.\
+- During multi-window drag, support for dragging out solo tabs to consolidate in another window, removing the source window in the process.\
+- Support vertical as well as horizontal alignment.\
+\
+
+\f0\b Version History
+\f1\b0 \
+\
+Version 1.3 (May 29, 2006)\
+- new feature: Unified tab style, compliments of Keith Blount\
+- new feature: allow multi-window drag config option (again from Keith).\
+- fixed bug: Palette installation/usage instructions were wrong.\
+- enhancement: exposed the
+\f3\fs20 representedTabViewItems
+\f1\fs24 method, which can be used to retrieve the order of the tabs as displayed in the control, since the underlying NSTabView does not get reordered during drag and drop rearrangement.\
+\
+Version 1.2 (April 20, 2006)\
+- new feature: multi-window drag and drop support.\
+- bug fixed: zombie issue with tabView:didCloseTabViewItem\
+- bugs fixed: some drawing issues around the progress indicators in tabs, and the add tab button.\
+- enhancement: the hide/show animation has been improved with less "flickering" of progress indicators during the hide and show.\
+\
+Version 1.1.2 (April 5, 2006)\
+- fixed bug: tabs of non-integer width resulted in occasional anti-aliased drawing issues of dividers between tabs in the Metal style (Thanks, Kent).\
+- added feature: delegate can now respond to -tabView:shouldCloseTabViewItem: and -tabView:willCloseTabViewItem:, and -tabView:didCloseTabViewItem: messages, so your app can take care of any needed setup/cleanup for these actions.\
+- fixed bug: tab close buttons now show down state when pressed down.\
+\
+Version 1.1.1 (March 16, 2006)\
+- fixed bug: Palette inspector would not reflect state of previously instantiated control. This has been fixed (Thanks, Guillaume).\
+- enhancement: Overflow button now highlights when mouse down (Thanks, Kent).\
+- fixed bug: when set to not close a solo tab, the close button would be hidden for the tab, but could still be closed if you clicked the tab in the right location. This has been fixed (Thanks, malcom).\
+\
+Version 1.1 (March 10, 2006)\
+- Bound the "title" of the cell to the "label" of the source tabview item. Just in case you wanted to change the label on the tab during the running of your application.\
+- PSMTabBarCell factored to support new tab "styles", or appearances in drawing. Now supported are the existing "Metal" style and a new "Aqua" style. Many thanks to David Smith, Seth Willits, and Chris Forsythe for their contributions!\
+- Control can be configured to "Hide for single tab", so it doesn't appear unless there are more than a single tab view present. Features animated show/hide behavior (that can be called anytime, and is called automatically in the case that a single tab exists). The show/hide behavior can also be set up to resize either the window (default) or a selected "partner view" to compensate for the lost height of the tab bar.\
+- Control can be configured for "Can close only tab" behavior. If set to NO, no close button will appear on a lone tab.\
+- Cells can be set to "size to fit", or given uniform min/max/optimum sizes.\
+- Added support for display of an icon and an object count, if the proper app design pattern is followed.\
+- Sweet animated drag-and-drop drawing!\
+- A few drawing bugs surrounding the progress indicators in cells were squished.\
+- New documentation, in case you found this read me a little pithy.\
+\
+Version 1.0 (December 2005)\
+Initial release of safari-like tab implementation.\
+\
+
+\f0\b The standard disavowal of this beautiful mess
+\f1\b0 \
+\
+I should note that portions of this source code were inspired by the Shiira project's implementation of Safari-style tabs. While I made some different design decisions, the drawing and some other aspects are only slight modifications of their excellent work. As such, I note their copyright under their BSD licence:\
+\
+\pard\tx960\tx1920\tx2880\tx3840\tx4800\tx5760\tx6720\tx7680\tx8640\tx9600\tx10560\tx11520\tx12480\tx13440\tx14400\tx15360\tx16320\tx17280\tx18240\tx19200\tx20160\tx21120\tx22080\tx23040\tx24000\tx24960\tx25920\tx26880\tx27840\tx28800\tx29760\tx30720\tx31680\tx32640\tx33600\tx34560\tx35520\tx36480\tx37440\tx38400\tx39360\tx40320\tx41280\tx42240\tx43200\tx44160\tx45120\tx46080\tx47040\tx48000\tx48960\tx49920\tx50880\tx51840\tx52800\tx53760\tx54720\tx55680\tx56640\tx57600\tx58560\tx59520\tx60480\tx61440\tx62400\tx63360\tx64320\tx65280\tx66240\tx67200\tx68160\tx69120\tx70080\tx71040\tx72000\tx72960\tx73920\tx74880\tx75840\tx76800\tx77760\tx78720\tx79680\tx80640\tx81600\tx82560\tx83520\tx84480\tx85440\tx86400\tx87360\tx88320\tx89280\tx90240\tx91200\tx92160\tx93120\tx94080\tx95040\tx96000\ql\qnatural\pardirnatural
+
+\f3\fs20 \cf4 Portions of this software Copyright 2004 The Shiira Project. All rights reserved.\
+\pard\tx960\tx1920\tx2880\tx3840\tx4800\tx5760\tx6720\tx7680\tx8640\tx9600\tx10560\tx11520\tx12480\tx13440\tx14400\tx15360\tx16320\tx17280\tx18240\tx19200\tx20160\tx21120\tx22080\tx23040\tx24000\tx24960\tx25920\tx26880\tx27840\tx28800\tx29760\tx30720\tx31680\tx32640\tx33600\tx34560\tx35520\tx36480\tx37440\tx38400\tx39360\tx40320\tx41280\tx42240\tx43200\tx44160\tx45120\tx46080\tx47040\tx48000\tx48960\tx49920\tx50880\tx51840\tx52800\tx53760\tx54720\tx55680\tx56640\tx57600\tx58560\tx59520\tx60480\tx61440\tx62400\tx63360\tx64320\tx65280\tx66240\tx67200\tx68160\tx69120\tx70080\tx71040\tx72000\tx72960\tx73920\tx74880\tx75840\tx76800\tx77760\tx78720\tx79680\tx80640\tx81600\tx82560\tx83520\tx84480\tx85440\tx86400\tx87360\tx88320\tx89280\tx90240\tx91200\tx92160\tx93120\tx94080\tx95040\tx96000\ql\qnatural\pardirnatural
+
+\f1\fs24 \cf0 Check them out at: http://hmdt-web.net/shiira/\
+\
+This source code is provided under BSD license, the conditions of which are listed below. I hope you'll make note somewhere in your about window or ReadMe stating the sweet coding goodness of Positive Spin Media and link to the fascinating and informative website at www.positivespinmedia.com\
+\
+\pard\pardeftab720\sa320\ql\qnatural
+\cf0 \CocoaLigature1 Copyright (c) 2005, Positive Spin Media\uc0\u8232 All rights reserved.\
+Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met:\
+\pard\tx220\tx720\pardeftab720\li720\fi-720\ql\qnatural
+\ls1\ilvl0\cf0 {\listtext \'a5 }Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer.\
+{\listtext \'a5 }Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution.\
+{\listtext \'a5 }Neither the name of Positive Spin Media nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission.\
+ \
+\pard\pardeftab720\sa320\ql\qnatural
+\cf0 THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.\CocoaLigature0 \
+} \ No newline at end of file
diff --git a/frontends/cocoa/PSMTabBarControl/ReadMe.rtfd/startpage.gif b/frontends/cocoa/PSMTabBarControl/ReadMe.rtfd/startpage.gif
new file mode 100644
index 000000000..8707a77ab
--- /dev/null
+++ b/frontends/cocoa/PSMTabBarControl/ReadMe.rtfd/startpage.gif
Binary files differ