aboutsummaryrefslogtreecommitdiffstats
path: root/Frameworks/PSMTabBar
diff options
context:
space:
mode:
Diffstat (limited to 'Frameworks/PSMTabBar')
-rw-r--r--Frameworks/PSMTabBar/Images/AdiumGradient.pngbin0 -> 93 bytes
-rw-r--r--Frameworks/PSMTabBar/Images/AquaTabCloseDirty_Front.pngbin0 -> 292 bytes
-rw-r--r--Frameworks/PSMTabBar/Images/AquaTabCloseDirty_Front_Pressed.pngbin0 -> 292 bytes
-rw-r--r--Frameworks/PSMTabBar/Images/AquaTabCloseDirty_Front_Rollover.pngbin0 -> 297 bytes
-rw-r--r--Frameworks/PSMTabBar/Images/AquaTabClose_Front.pngbin0 -> 307 bytes
-rw-r--r--Frameworks/PSMTabBar/Images/AquaTabClose_Front_Pressed.pngbin0 -> 310 bytes
-rw-r--r--Frameworks/PSMTabBar/Images/AquaTabClose_Front_Rollover.pngbin0 -> 317 bytes
-rw-r--r--Frameworks/PSMTabBar/Images/AquaTabNew.pngbin0 -> 371 bytes
-rw-r--r--Frameworks/PSMTabBar/Images/AquaTabNewPressed.pngbin0 -> 380 bytes
-rw-r--r--Frameworks/PSMTabBar/Images/AquaTabNewRollover.pngbin0 -> 380 bytes
-rw-r--r--Frameworks/PSMTabBar/Images/AquaTabsBackground.pngbin0 -> 380 bytes
-rw-r--r--Frameworks/PSMTabBar/Images/AquaTabsDown.pngbin0 -> 192 bytes
-rw-r--r--Frameworks/PSMTabBar/Images/AquaTabsDownGraphite.pngbin0 -> 240 bytes
-rw-r--r--Frameworks/PSMTabBar/Images/AquaTabsDownNonKey.pngbin0 -> 182 bytes
-rw-r--r--Frameworks/PSMTabBar/Images/AquaTabsSeparator.pngbin0 -> 159 bytes
-rw-r--r--Frameworks/PSMTabBar/Images/AquaTabsSeparatorDown.pngbin0 -> 175 bytes
-rw-r--r--Frameworks/PSMTabBar/Images/TabClose_Dirty.pngbin0 -> 327 bytes
-rw-r--r--Frameworks/PSMTabBar/Images/TabClose_Dirty_Pressed.pngbin0 -> 320 bytes
-rw-r--r--Frameworks/PSMTabBar/Images/TabClose_Dirty_Rollover.pngbin0 -> 320 bytes
-rw-r--r--Frameworks/PSMTabBar/Images/TabClose_Front.pngbin0 -> 309 bytes
-rw-r--r--Frameworks/PSMTabBar/Images/TabClose_Front_Pressed.pngbin0 -> 310 bytes
-rw-r--r--Frameworks/PSMTabBar/Images/TabClose_Front_Rollover.pngbin0 -> 308 bytes
-rw-r--r--Frameworks/PSMTabBar/Images/TabNewMetal.pngbin0 -> 380 bytes
-rw-r--r--Frameworks/PSMTabBar/Images/TabNewMetalPressed.pngbin0 -> 380 bytes
-rw-r--r--Frameworks/PSMTabBar/Images/TabNewMetalRollover.pngbin0 -> 384 bytes
-rw-r--r--Frameworks/PSMTabBar/Images/overflowImage.pngbin0 -> 256 bytes
-rw-r--r--Frameworks/PSMTabBar/Images/overflowImagePressed.pngbin0 -> 250 bytes
-rw-r--r--Frameworks/PSMTabBar/Images/pi.pngbin0 -> 564 bytes
-rwxr-xr-xFrameworks/PSMTabBar/NSBezierPath_AMShading.h23
-rwxr-xr-xFrameworks/PSMTabBar/NSBezierPath_AMShading.m127
-rw-r--r--Frameworks/PSMTabBar/NSString_AITruncation.h12
-rw-r--r--Frameworks/PSMTabBar/NSString_AITruncation.m34
-rw-r--r--Frameworks/PSMTabBar/PSMOverflowPopUpButton.h28
-rw-r--r--Frameworks/PSMTabBar/PSMOverflowPopUpButton.m161
-rw-r--r--Frameworks/PSMTabBar/PSMProgressIndicator.h23
-rw-r--r--Frameworks/PSMTabBar/PSMProgressIndicator.m27
-rw-r--r--Frameworks/PSMTabBar/PSMRolloverButton.h29
-rw-r--r--Frameworks/PSMTabBar/PSMRolloverButton.m183
-rw-r--r--Frameworks/PSMTabBar/PSMTabBarCell.h117
-rw-r--r--Frameworks/PSMTabBar/PSMTabBarCell.m535
-rw-r--r--Frameworks/PSMTabBar/PSMTabBarControl.h235
-rw-r--r--Frameworks/PSMTabBar/PSMTabBarControl.m2120
-rw-r--r--Frameworks/PSMTabBar/PSMTabBarController.h33
-rw-r--r--Frameworks/PSMTabBar/PSMTabBarController.m665
-rw-r--r--Frameworks/PSMTabBar/PSMTabDragAssistant.h100
-rw-r--r--Frameworks/PSMTabBar/PSMTabDragAssistant.m872
-rw-r--r--Frameworks/PSMTabBar/PSMTabDragView.h20
-rw-r--r--Frameworks/PSMTabBar/PSMTabDragView.m68
-rw-r--r--Frameworks/PSMTabBar/PSMTabDragWindow.h20
-rw-r--r--Frameworks/PSMTabBar/PSMTabDragWindow.m51
-rw-r--r--Frameworks/PSMTabBar/PSMTabDragWindowController.h33
-rw-r--r--Frameworks/PSMTabBar/PSMTabDragWindowController.m119
-rw-r--r--Frameworks/PSMTabBar/PSMTabStyle.h57
-rw-r--r--Frameworks/PSMTabBar/Styles/PSMAdiumTabStyle.h39
-rw-r--r--Frameworks/PSMTabBar/Styles/PSMAdiumTabStyle.m1057
-rw-r--r--Frameworks/PSMTabBar/Styles/PSMAquaTabStyle.h38
-rw-r--r--Frameworks/PSMTabBar/Styles/PSMAquaTabStyle.m579
-rw-r--r--Frameworks/PSMTabBar/Styles/PSMCardTabStyle.h32
-rw-r--r--Frameworks/PSMTabBar/Styles/PSMCardTabStyle.m649
-rw-r--r--Frameworks/PSMTabBar/Styles/PSMMetalTabStyle.h34
-rw-r--r--Frameworks/PSMTabBar/Styles/PSMMetalTabStyle.m656
-rw-r--r--Frameworks/PSMTabBar/Styles/PSMUnifiedTabStyle.h30
-rw-r--r--Frameworks/PSMTabBar/Styles/PSMUnifiedTabStyle.m603
63 files changed, 9409 insertions, 0 deletions
diff --git a/Frameworks/PSMTabBar/Images/AdiumGradient.png b/Frameworks/PSMTabBar/Images/AdiumGradient.png
new file mode 100644
index 00000000..d410a88a
--- /dev/null
+++ b/Frameworks/PSMTabBar/Images/AdiumGradient.png
Binary files differ
diff --git a/Frameworks/PSMTabBar/Images/AquaTabCloseDirty_Front.png b/Frameworks/PSMTabBar/Images/AquaTabCloseDirty_Front.png
new file mode 100644
index 00000000..77d22050
--- /dev/null
+++ b/Frameworks/PSMTabBar/Images/AquaTabCloseDirty_Front.png
Binary files differ
diff --git a/Frameworks/PSMTabBar/Images/AquaTabCloseDirty_Front_Pressed.png b/Frameworks/PSMTabBar/Images/AquaTabCloseDirty_Front_Pressed.png
new file mode 100644
index 00000000..197ea95c
--- /dev/null
+++ b/Frameworks/PSMTabBar/Images/AquaTabCloseDirty_Front_Pressed.png
Binary files differ
diff --git a/Frameworks/PSMTabBar/Images/AquaTabCloseDirty_Front_Rollover.png b/Frameworks/PSMTabBar/Images/AquaTabCloseDirty_Front_Rollover.png
new file mode 100644
index 00000000..2dfe5777
--- /dev/null
+++ b/Frameworks/PSMTabBar/Images/AquaTabCloseDirty_Front_Rollover.png
Binary files differ
diff --git a/Frameworks/PSMTabBar/Images/AquaTabClose_Front.png b/Frameworks/PSMTabBar/Images/AquaTabClose_Front.png
new file mode 100644
index 00000000..02b72d39
--- /dev/null
+++ b/Frameworks/PSMTabBar/Images/AquaTabClose_Front.png
Binary files differ
diff --git a/Frameworks/PSMTabBar/Images/AquaTabClose_Front_Pressed.png b/Frameworks/PSMTabBar/Images/AquaTabClose_Front_Pressed.png
new file mode 100644
index 00000000..f81125a0
--- /dev/null
+++ b/Frameworks/PSMTabBar/Images/AquaTabClose_Front_Pressed.png
Binary files differ
diff --git a/Frameworks/PSMTabBar/Images/AquaTabClose_Front_Rollover.png b/Frameworks/PSMTabBar/Images/AquaTabClose_Front_Rollover.png
new file mode 100644
index 00000000..4f6b865f
--- /dev/null
+++ b/Frameworks/PSMTabBar/Images/AquaTabClose_Front_Rollover.png
Binary files differ
diff --git a/Frameworks/PSMTabBar/Images/AquaTabNew.png b/Frameworks/PSMTabBar/Images/AquaTabNew.png
new file mode 100644
index 00000000..10a83705
--- /dev/null
+++ b/Frameworks/PSMTabBar/Images/AquaTabNew.png
Binary files differ
diff --git a/Frameworks/PSMTabBar/Images/AquaTabNewPressed.png b/Frameworks/PSMTabBar/Images/AquaTabNewPressed.png
new file mode 100644
index 00000000..cb4dd10f
--- /dev/null
+++ b/Frameworks/PSMTabBar/Images/AquaTabNewPressed.png
Binary files differ
diff --git a/Frameworks/PSMTabBar/Images/AquaTabNewRollover.png b/Frameworks/PSMTabBar/Images/AquaTabNewRollover.png
new file mode 100644
index 00000000..4d469f8a
--- /dev/null
+++ b/Frameworks/PSMTabBar/Images/AquaTabNewRollover.png
Binary files differ
diff --git a/Frameworks/PSMTabBar/Images/AquaTabsBackground.png b/Frameworks/PSMTabBar/Images/AquaTabsBackground.png
new file mode 100644
index 00000000..b9cd1d0f
--- /dev/null
+++ b/Frameworks/PSMTabBar/Images/AquaTabsBackground.png
Binary files differ
diff --git a/Frameworks/PSMTabBar/Images/AquaTabsDown.png b/Frameworks/PSMTabBar/Images/AquaTabsDown.png
new file mode 100644
index 00000000..6fed84c6
--- /dev/null
+++ b/Frameworks/PSMTabBar/Images/AquaTabsDown.png
Binary files differ
diff --git a/Frameworks/PSMTabBar/Images/AquaTabsDownGraphite.png b/Frameworks/PSMTabBar/Images/AquaTabsDownGraphite.png
new file mode 100644
index 00000000..15bcc19c
--- /dev/null
+++ b/Frameworks/PSMTabBar/Images/AquaTabsDownGraphite.png
Binary files differ
diff --git a/Frameworks/PSMTabBar/Images/AquaTabsDownNonKey.png b/Frameworks/PSMTabBar/Images/AquaTabsDownNonKey.png
new file mode 100644
index 00000000..df2c1365
--- /dev/null
+++ b/Frameworks/PSMTabBar/Images/AquaTabsDownNonKey.png
Binary files differ
diff --git a/Frameworks/PSMTabBar/Images/AquaTabsSeparator.png b/Frameworks/PSMTabBar/Images/AquaTabsSeparator.png
new file mode 100644
index 00000000..be82692d
--- /dev/null
+++ b/Frameworks/PSMTabBar/Images/AquaTabsSeparator.png
Binary files differ
diff --git a/Frameworks/PSMTabBar/Images/AquaTabsSeparatorDown.png b/Frameworks/PSMTabBar/Images/AquaTabsSeparatorDown.png
new file mode 100644
index 00000000..72b7878b
--- /dev/null
+++ b/Frameworks/PSMTabBar/Images/AquaTabsSeparatorDown.png
Binary files differ
diff --git a/Frameworks/PSMTabBar/Images/TabClose_Dirty.png b/Frameworks/PSMTabBar/Images/TabClose_Dirty.png
new file mode 100644
index 00000000..60a25ba3
--- /dev/null
+++ b/Frameworks/PSMTabBar/Images/TabClose_Dirty.png
Binary files differ
diff --git a/Frameworks/PSMTabBar/Images/TabClose_Dirty_Pressed.png b/Frameworks/PSMTabBar/Images/TabClose_Dirty_Pressed.png
new file mode 100644
index 00000000..978dc1c7
--- /dev/null
+++ b/Frameworks/PSMTabBar/Images/TabClose_Dirty_Pressed.png
Binary files differ
diff --git a/Frameworks/PSMTabBar/Images/TabClose_Dirty_Rollover.png b/Frameworks/PSMTabBar/Images/TabClose_Dirty_Rollover.png
new file mode 100644
index 00000000..7b8924da
--- /dev/null
+++ b/Frameworks/PSMTabBar/Images/TabClose_Dirty_Rollover.png
Binary files differ
diff --git a/Frameworks/PSMTabBar/Images/TabClose_Front.png b/Frameworks/PSMTabBar/Images/TabClose_Front.png
new file mode 100644
index 00000000..e7bf88e7
--- /dev/null
+++ b/Frameworks/PSMTabBar/Images/TabClose_Front.png
Binary files differ
diff --git a/Frameworks/PSMTabBar/Images/TabClose_Front_Pressed.png b/Frameworks/PSMTabBar/Images/TabClose_Front_Pressed.png
new file mode 100644
index 00000000..feaf7281
--- /dev/null
+++ b/Frameworks/PSMTabBar/Images/TabClose_Front_Pressed.png
Binary files differ
diff --git a/Frameworks/PSMTabBar/Images/TabClose_Front_Rollover.png b/Frameworks/PSMTabBar/Images/TabClose_Front_Rollover.png
new file mode 100644
index 00000000..865bd1fb
--- /dev/null
+++ b/Frameworks/PSMTabBar/Images/TabClose_Front_Rollover.png
Binary files differ
diff --git a/Frameworks/PSMTabBar/Images/TabNewMetal.png b/Frameworks/PSMTabBar/Images/TabNewMetal.png
new file mode 100644
index 00000000..be02d708
--- /dev/null
+++ b/Frameworks/PSMTabBar/Images/TabNewMetal.png
Binary files differ
diff --git a/Frameworks/PSMTabBar/Images/TabNewMetalPressed.png b/Frameworks/PSMTabBar/Images/TabNewMetalPressed.png
new file mode 100644
index 00000000..18118ec3
--- /dev/null
+++ b/Frameworks/PSMTabBar/Images/TabNewMetalPressed.png
Binary files differ
diff --git a/Frameworks/PSMTabBar/Images/TabNewMetalRollover.png b/Frameworks/PSMTabBar/Images/TabNewMetalRollover.png
new file mode 100644
index 00000000..b1308164
--- /dev/null
+++ b/Frameworks/PSMTabBar/Images/TabNewMetalRollover.png
Binary files differ
diff --git a/Frameworks/PSMTabBar/Images/overflowImage.png b/Frameworks/PSMTabBar/Images/overflowImage.png
new file mode 100644
index 00000000..2b762555
--- /dev/null
+++ b/Frameworks/PSMTabBar/Images/overflowImage.png
Binary files differ
diff --git a/Frameworks/PSMTabBar/Images/overflowImagePressed.png b/Frameworks/PSMTabBar/Images/overflowImagePressed.png
new file mode 100644
index 00000000..b3918b34
--- /dev/null
+++ b/Frameworks/PSMTabBar/Images/overflowImagePressed.png
Binary files differ
diff --git a/Frameworks/PSMTabBar/Images/pi.png b/Frameworks/PSMTabBar/Images/pi.png
new file mode 100644
index 00000000..4d598dc7
--- /dev/null
+++ b/Frameworks/PSMTabBar/Images/pi.png
Binary files differ
diff --git a/Frameworks/PSMTabBar/NSBezierPath_AMShading.h b/Frameworks/PSMTabBar/NSBezierPath_AMShading.h
new file mode 100755
index 00000000..aed6f13b
--- /dev/null
+++ b/Frameworks/PSMTabBar/NSBezierPath_AMShading.h
@@ -0,0 +1,23 @@
+//
+// 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/Frameworks/PSMTabBar/NSBezierPath_AMShading.m b/Frameworks/PSMTabBar/NSBezierPath_AMShading.m
new file mode 100755
index 00000000..73213f3b
--- /dev/null
+++ b/Frameworks/PSMTabBar/NSBezierPath_AMShading.m
@@ -0,0 +1,127 @@
+//
+// 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/Frameworks/PSMTabBar/NSString_AITruncation.h b/Frameworks/PSMTabBar/NSString_AITruncation.h
new file mode 100644
index 00000000..cbcbf2c7
--- /dev/null
+++ b/Frameworks/PSMTabBar/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/Frameworks/PSMTabBar/NSString_AITruncation.m b/Frameworks/PSMTabBar/NSString_AITruncation.m
new file mode 100644
index 00000000..567c78f2
--- /dev/null
+++ b/Frameworks/PSMTabBar/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] autorelease];
+ }
+
+ return returnString;
+}
+
+@end
diff --git a/Frameworks/PSMTabBar/PSMOverflowPopUpButton.h b/Frameworks/PSMTabBar/PSMOverflowPopUpButton.h
new file mode 100644
index 00000000..19ce95f1
--- /dev/null
+++ b/Frameworks/PSMTabBar/PSMOverflowPopUpButton.h
@@ -0,0 +1,28 @@
+//
+// 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/Frameworks/PSMTabBar/PSMOverflowPopUpButton.m b/Frameworks/PSMTabBar/PSMOverflowPopUpButton.m
new file mode 100644
index 00000000..cc96b910
--- /dev/null
+++ b/Frameworks/PSMTabBar/PSMOverflowPopUpButton.m
@@ -0,0 +1,161 @@
+//
+// PSMOverflowPopUpButton.m
+// PSMTabBarControl
+//
+// Created by John Pannell on 11/4/05.
+// Copyright 2005 Positive Spin Media. All rights reserved.
+//
+
+#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]) {
+ [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)dealloc
+{
+ [_PSMTabBarOverflowPopUpImage release];
+ [_PSMTabBarOverflowDownPopUpImage release];
+ [super dealloc];
+}
+
+- (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 compositeToPoint:drawPoint operation:NSCompositeSourceOver 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] compositeToPoint:drawPoint operation:NSCompositeSourceOver 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"] retain];
+ _PSMTabBarOverflowDownPopUpImage = [[aDecoder decodeObjectForKey:@"PSMTabBarOverflowDownPopUpImage"] retain];
+ [self setAnimatingAlternateImage:[aDecoder decodeBoolForKey:@"PSMTabBarOverflowAnimatingAlternateImage"]];
+ }
+ }
+ return self;
+}
+
+@end
diff --git a/Frameworks/PSMTabBar/PSMProgressIndicator.h b/Frameworks/PSMTabBar/PSMProgressIndicator.h
new file mode 100644
index 00000000..8f56bd73
--- /dev/null
+++ b/Frameworks/PSMTabBar/PSMProgressIndicator.h
@@ -0,0 +1,23 @@
+//
+// PSMProgressIndicator.h
+// PSMTabBarControl
+//
+// Created by John Pannell on 2/23/06.
+// Copyright 2006 Positive Spin Media. All rights reserved.
+//
+
+#import <Cocoa/Cocoa.h>
+#import "PSMTabBarControl.h"
+
+
+@interface PSMProgressIndicator : NSProgressIndicator {
+
+}
+
+@end
+
+@interface PSMTabBarControl (LayoutPlease)
+
+- (void)update;
+
+@end \ No newline at end of file
diff --git a/Frameworks/PSMTabBar/PSMProgressIndicator.m b/Frameworks/PSMTabBar/PSMProgressIndicator.m
new file mode 100644
index 00000000..f79852a2
--- /dev/null
+++ b/Frameworks/PSMTabBar/PSMProgressIndicator.m
@@ -0,0 +1,27 @@
+//
+// PSMProgressIndicator.m
+// PSMTabBarControl
+//
+// Created by John Pannell on 2/23/06.
+// Copyright 2006 Positive Spin Media. All rights reserved.
+//
+
+#import "PSMProgressIndicator.h"
+
+@implementation PSMProgressIndicator
+
+// 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/Frameworks/PSMTabBar/PSMRolloverButton.h b/Frameworks/PSMTabBar/PSMRolloverButton.h
new file mode 100644
index 00000000..d78b47c2
--- /dev/null
+++ b/Frameworks/PSMTabBar/PSMRolloverButton.h
@@ -0,0 +1,29 @@
+//
+// 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/Frameworks/PSMTabBar/PSMRolloverButton.m b/Frameworks/PSMTabBar/PSMRolloverButton.m
new file mode 100644
index 00000000..988abbb2
--- /dev/null
+++ b/Frameworks/PSMTabBar/PSMRolloverButton.m
@@ -0,0 +1,183 @@
+//
+// 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];
+
+ [super dealloc];
+}
+
+// the regular image
+- (void)setUsualImage:(NSImage *)newImage
+{
+ [newImage retain];
+ [_usualImage release];
+ _usualImage = newImage;
+
+ [self setImage:_usualImage];
+}
+
+- (NSImage *)usualImage
+{
+ return _usualImage;
+}
+
+- (void)setRolloverImage:(NSImage *)newImage
+{
+ [newImage retain];
+ [_rolloverImage release];
+ _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:[[self window] convertScreenToBase:[NSEvent mouseLocation]]
+ fromView:nil];
+ BOOL mouseInside = NSPointInRect(localPoint, trackRect);
+
+ _myTrackingRectTag = [self addTrackingRect:trackRect owner:self userData:nil assumeInside:mouseInside];
+ if (mouseInside)
+ [self mouseEntered:nil];
+ else
+ [self mouseExited:nil];
+}
+
+- (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 encodeInteger:_myTrackingRectTag forKey:@"myTrackingRectTag"];
+ }
+}
+
+- (id)initWithCoder:(NSCoder *)aDecoder {
+ self = [super initWithCoder:aDecoder];
+ if (self) {
+ if ([aDecoder allowsKeyedCoding]) {
+ _rolloverImage = [[aDecoder decodeObjectForKey:@"rolloverImage"] retain];
+ _usualImage = [[aDecoder decodeObjectForKey:@"usualImage"] retain];
+ _myTrackingRectTag = [aDecoder decodeIntegerForKey:@"myTrackingRectTag"];
+ }
+ }
+ return self;
+}
+
+
+@end
diff --git a/Frameworks/PSMTabBar/PSMTabBarCell.h b/Frameworks/PSMTabBar/PSMTabBarCell.h
new file mode 100644
index 00000000..519c7ac4
--- /dev/null
+++ b/Frameworks/PSMTabBar/PSMTabBarCell.h
@@ -0,0 +1,117 @@
+//
+// 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"
+
+@class PSMTabBarControl;
+@class PSMProgressIndicator;
+
+@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/Frameworks/PSMTabBar/PSMTabBarCell.m b/Frameworks/PSMTabBar/PSMTabBarCell.m
new file mode 100644
index 00000000..d35f68ee
--- /dev/null
+++ b/Frameworks/PSMTabBar/PSMTabBarCell.m
@@ -0,0 +1,535 @@
+//
+// 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"
+
+@interface PSMTabBarControl (Private)
+- (void)update;
+@end
+
+@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
+{
+ [_countColor release];
+
+ [_indicator removeFromSuperviewWithoutNeedingDisplay];
+
+ [_indicator release];
+ [super dealloc];
+}
+
+#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>)[_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] & NSCommandKeyMask));
+}
+
+- (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 release];
+ _countColor = [color retain];
+}
+
+- (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>)[_controlView style] indicatorRectForTabCell:self];
+}
+
+- (NSRect)closeButtonRectForFrame:(NSRect)cellFrame
+{
+ return [(id <PSMTabStyle>)[_controlView style] closeButtonRectForTabCell:self withFrame:cellFrame];
+}
+
+- (CGFloat)minimumWidthOfCell
+{
+ return [(id <PSMTabStyle>)[_controlView style] minimumWidthOfTabCell:self];
+}
+
+- (CGFloat)desiredWidthOfCell
+{
+ return [(id <PSMTabStyle>)[_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, NSCompositeSourceAtop);
+ return;
+ }
+
+ [(id <PSMTabStyle>)[_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] & NSAlternateKeyMask))
+ [_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] autorelease];
+ [_controlView unlockFocus];
+ NSImage *image = [[[NSImage alloc] initWithSize:[rep size]] autorelease];
+ [image addRepresentation:rep];
+ NSImage *returnImage = [[[NSImage alloc] initWithSize:[rep size]] autorelease];
+ [returnImage lockFocus];
+ [image compositeToPoint:NSMakePoint(0.0, 0.0) operation:NSCompositeSourceOver 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 compositeToPoint:indicatorPoint operation:NSCompositeSourceOver fraction:1.0];
+ [returnImage unlockFocus];
+ [pi release];
+ }
+ 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"] retain];
+ _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/Frameworks/PSMTabBar/PSMTabBarControl.h b/Frameworks/PSMTabBar/PSMTabBarControl.h
new file mode 100644
index 00000000..c61b92f4
--- /dev/null
+++ b/Frameworks/PSMTabBar/PSMTabBarControl.h
@@ -0,0 +1,235 @@
+//
+// 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, PSMRolloverButton, PSMTabBarCell, 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;
+
+// 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/Frameworks/PSMTabBar/PSMTabBarControl.m b/Frameworks/PSMTabBar/PSMTabBarControl.m
new file mode 100644
index 00000000..9242f8a1
--- /dev/null
+++ b/Frameworks/PSMTabBar/PSMTabBarControl.m
@@ -0,0 +1,2120 @@
+//
+// PSMTabBarControl.m
+// PSMTabBarControl
+//
+// Created by John Pannell on 10/13/05.
+// Copyright 2005 Positive Spin Media. All rights reserved.
+//
+
+#import "PSMTabBarControl.h"
+#import "PSMTabBarCell.h"
+#import "PSMOverflowPopUpButton.h"
+#import "PSMRolloverButton.h"
+#import "PSMTabStyle.h"
+#import "PSMMetalTabStyle.h"
+#import "PSMAquaTabStyle.h"
+#import "PSMUnifiedTabStyle.h"
+#import "PSMCardTabStyle.h"
+#import "PSMAdiumTabStyle.h"
+#import "PSMTabDragAssistant.h"
+#import "PSMTabBarController.h"
+
+#include <Carbon/Carbon.h> /* for GetKeys() and KeyMap */
+#include <bitstring.h>
+
+@interface PSMTabBarControl (Private)
+
+ // constructor/destructor
+- (void)initAddedProperties;
+
+ // accessors
+- (NSEvent *)lastMouseDownEvent;
+- (void)setLastMouseDownEvent:(NSEvent *)event;
+
+ // contents
+- (void)addTabViewItem:(NSTabViewItem *)item;
+- (void)removeTabForCell:(PSMTabBarCell *)cell;
+
+ // draw
+- (void)update;
+- (void)update:(BOOL)animate;
+- (void)_setupTrackingRectsForCell:(PSMTabBarCell *)cell;
+- (void)_positionOverflowMenu;
+- (void)_checkWindowFrame;
+
+ // actions
+- (void)overflowMenuAction:(id)sender;
+- (void)closeTabClick:(id)sender;
+- (void)tabClick:(id)sender;
+- (void)tabNothing:(id)sender;
+
+ // notification handlers
+- (void)frameDidChange:(NSNotification *)notification;
+- (void)windowDidMove:(NSNotification *)aNotification;
+- (void)windowDidUpdate:(NSNotification *)notification;
+
+ // NSTabView delegate
+- (void)tabView:(NSTabView *)tabView didSelectTabViewItem:(NSTabViewItem *)tabViewItem;
+- (BOOL)tabView:(NSTabView *)tabView shouldSelectTabViewItem:(NSTabViewItem *)tabViewItem;
+- (void)tabView:(NSTabView *)tabView willSelectTabViewItem:(NSTabViewItem *)tabViewItem;
+- (void)tabViewDidChangeNumberOfTabViewItems:(NSTabView *)tabView;
+
+ // archiving
+- (void)encodeWithCoder:(NSCoder *)aCoder;
+- (id)initWithCoder:(NSCoder *)aDecoder;
+
+ // convenience
+- (void)_bindPropertiesForCell:(PSMTabBarCell *)cell andTabViewItem:(NSTabViewItem *)item;
+- (id)cellForPoint:(NSPoint)point cellFrame:(NSRectPointer)outFrame;
+
+- (void)_animateCells:(NSTimer *)timer;
+@end
+
+@implementation PSMTabBarControl
+
+#pragma mark -
+#pragma mark Characteristics
++ (NSBundle *)bundle;
+{
+ static NSBundle *bundle = nil;
+ if (!bundle) bundle = [NSBundle bundleForClass:[PSMTabBarControl class]];
+ return bundle;
+}
+
+/*!
+ @method availableCellWidth
+ @abstract The number of pixels available for cells
+ @discussion Calculates the number of pixels available for cells based on margins and the window resize badge.
+ @returns Returns the amount of space for cells.
+*/
+
+- (CGFloat)availableCellWidth
+{
+ return [self frame].size.width - [style leftMarginForTabBarControl] - [style rightMarginForTabBarControl] - _resizeAreaCompensation;
+}
+
+/*!
+ @method genericCellRect
+ @abstract The basic rect for a tab cell.
+ @discussion Creates a generic frame for a tab cell based on the current control state.
+ @returns Returns a basic rect for a tab cell.
+*/
+
+- (NSRect)genericCellRect
+{
+ NSRect aRect=[self frame];
+ aRect.origin.x = [style leftMarginForTabBarControl];
+ aRect.origin.y = 0.0;
+ aRect.size.width = [self availableCellWidth];
+ aRect.size.height = [style tabCellHeight];
+ return aRect;
+}
+
+#pragma mark -
+#pragma mark Constructor/destructor
+
+- (void)initAddedProperties
+{
+ _cells = [[NSMutableArray alloc] initWithCapacity:10];
+ _controller = [[PSMTabBarController alloc] initWithTabBarControl:self];
+ _animationTimer = nil;
+
+ // default config
+ _currentStep = kPSMIsNotBeingResized;
+ _orientation = PSMTabBarHorizontalOrientation;
+ _canCloseOnlyTab = NO;
+ _disableTabClose = NO;
+ _showAddTabButton = NO;
+ _hideForSingleTab = NO;
+ _sizeCellsToFit = NO;
+ _isHidden = NO;
+ _awakenedFromNib = NO;
+ _automaticallyAnimates = NO;
+ _useOverflowMenu = YES;
+ _allowsBackgroundTabClosing = YES;
+ _allowsResizing = NO;
+ _selectsTabsOnMouseDown = NO;
+ _alwaysShowActiveTab = NO;
+ _allowsScrubbing = NO;
+ _cellMinWidth = 100;
+ _cellMaxWidth = 280;
+ _cellOptimumWidth = 130;
+ _tearOffStyle = PSMTabBarTearOffAlphaWindow;
+ style = [[PSMMetalTabStyle alloc] init];
+
+ // the overflow button/menu
+ NSRect overflowButtonRect = NSMakeRect([self frame].size.width - [style rightMarginForTabBarControl] + 1, 0, [style rightMarginForTabBarControl] - 1, [self frame].size.height);
+ _overflowPopUpButton = [[PSMOverflowPopUpButton alloc] initWithFrame:overflowButtonRect pullsDown:YES];
+ [_overflowPopUpButton setAutoresizingMask:NSViewNotSizable | NSViewMinXMargin];
+ [_overflowPopUpButton setHidden:YES];
+ [self addSubview:_overflowPopUpButton];
+ [self _positionOverflowMenu];
+
+ // new tab button
+ NSRect addTabButtonRect = NSMakeRect([self frame].size.width - [style rightMarginForTabBarControl] + 1, 3.0, 16.0, 16.0);
+ _addTabButton = [[PSMRolloverButton alloc] initWithFrame:addTabButtonRect];
+ if (_addTabButton) {
+ NSImage *newButtonImage = [style addTabButtonImage];
+ if (newButtonImage) {
+ [_addTabButton setUsualImage:newButtonImage];
+ }
+ newButtonImage = [style addTabButtonPressedImage];
+ if (newButtonImage) {
+ [_addTabButton setAlternateImage:newButtonImage];
+ }
+ newButtonImage = [style addTabButtonRolloverImage];
+ if (newButtonImage) {
+ [_addTabButton setRolloverImage:newButtonImage];
+ }
+ [_addTabButton setTitle:@""];
+ [_addTabButton setImagePosition:NSImageOnly];
+ [_addTabButton setButtonType:NSMomentaryChangeButton];
+ [_addTabButton setBordered:NO];
+ [_addTabButton setBezelStyle:NSShadowlessSquareBezelStyle];
+ [self addSubview:_addTabButton];
+
+ if (_showAddTabButton) {
+ [_addTabButton setHidden:NO];
+ } else {
+ [_addTabButton setHidden:YES];
+ }
+ [_addTabButton setNeedsDisplay:YES];
+ }
+}
+
+- (id)initWithFrame:(NSRect)frame
+{
+ self = [super initWithFrame:frame];
+ if (self) {
+ // Initialization
+ [self initAddedProperties];
+ [self registerForDraggedTypes:[NSArray arrayWithObjects:@"PSMTabBarControlItemPBType", nil]];
+
+ // resize
+ [self setPostsFrameChangedNotifications:YES];
+ [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(frameDidChange:) name:NSViewFrameDidChangeNotification object:self];
+ }
+ [self setTarget:self];
+ return self;
+}
+
+- (void)dealloc
+{
+ [[NSNotificationCenter defaultCenter] removeObserver:self];
+
+ //stop any animations that may be running
+ [_animationTimer invalidate];
+ [_animationTimer release]; _animationTimer = nil;
+
+ [_showHideAnimationTimer invalidate];
+ [_showHideAnimationTimer release]; _showHideAnimationTimer = nil;
+
+ //Also unwind the spring, if it's wound.
+ [_springTimer invalidate];
+ [_springTimer release]; _springTimer = nil;
+
+ //unbind all the items to prevent crashing
+ //not sure if this is necessary or not
+ NSArray *cells = [NSArray arrayWithArray:_cells]; // create a copy as we will change the original array while being enumerated
+ NSEnumerator *enumerator = [cells objectEnumerator];
+ PSMTabBarCell *nextCell;
+ while ( (nextCell = [enumerator nextObject]) ) {
+ [self removeTabForCell:nextCell];
+ }
+
+ [_overflowPopUpButton release];
+ [_cells release];
+ [_controller release];
+ [tabView release];
+ [_addTabButton release];
+ [partnerView release];
+ [_lastMouseDownEvent release];
+ [style release];
+
+ [self unregisterDraggedTypes];
+
+ [super dealloc];
+}
+
+- (void)awakeFromNib
+{
+ // build cells from existing tab view items
+ NSArray *existingItems = [tabView tabViewItems];
+ NSEnumerator *e = [existingItems objectEnumerator];
+ NSTabViewItem *item;
+ while ( (item = [e nextObject]) ) {
+ if (![[self representedTabViewItems] containsObject:item]) {
+ [self addTabViewItem:item];
+ }
+ }
+}
+
+- (void)viewWillMoveToWindow:(NSWindow *)aWindow {
+ NSNotificationCenter *center = [NSNotificationCenter defaultCenter];
+
+ [center removeObserver:self name:NSWindowDidBecomeKeyNotification object:nil];
+ [center removeObserver:self name:NSWindowDidResignKeyNotification object:nil];
+ [center removeObserver:self name:NSWindowDidUpdateNotification object:nil];
+ [center removeObserver:self name:NSWindowDidMoveNotification object:nil];
+
+ if (_showHideAnimationTimer) {
+ [_showHideAnimationTimer invalidate];
+ [_showHideAnimationTimer release]; _showHideAnimationTimer = nil;
+ }
+
+ if (aWindow) {
+ [center addObserver:self selector:@selector(windowStatusDidChange:) name:NSWindowDidBecomeKeyNotification object:aWindow];
+ [center addObserver:self selector:@selector(windowStatusDidChange:) name:NSWindowDidResignKeyNotification object:aWindow];
+ [center addObserver:self selector:@selector(windowDidUpdate:) name:NSWindowDidUpdateNotification object:aWindow];
+ [center addObserver:self selector:@selector(windowDidMove:) name:NSWindowDidMoveNotification object:aWindow];
+ }
+}
+
+- (void)windowStatusDidChange:(NSNotification *)notification
+{
+ [self setNeedsDisplay:YES];
+}
+
+#pragma mark -
+#pragma mark Accessors
+
+- (NSMutableArray *)cells
+{
+ return _cells;
+}
+
+- (NSEvent *)lastMouseDownEvent
+{
+ return _lastMouseDownEvent;
+}
+
+- (void)setLastMouseDownEvent:(NSEvent *)event
+{
+ [event retain];
+ [_lastMouseDownEvent release];
+ _lastMouseDownEvent = event;
+}
+
+- (id)delegate
+{
+ return delegate;
+}
+
+- (void)setDelegate:(id)object
+{
+ delegate = object;
+
+ NSMutableArray *types = [NSMutableArray arrayWithObject:@"PSMTabBarControlItemPBType"];
+
+ //Update the allowed drag types
+ if ([self delegate] && [[self delegate] respondsToSelector:@selector(allowedDraggedTypesForTabView:)]) {
+ [types addObjectsFromArray:[[self delegate] allowedDraggedTypesForTabView:tabView]];
+ }
+ [self unregisterDraggedTypes];
+ [self registerForDraggedTypes:types];
+}
+
+- (NSTabView *)tabView
+{
+ return tabView;
+}
+
+- (void)setTabView:(NSTabView *)view
+{
+ [view retain];
+ [tabView release];
+ tabView = view;
+}
+
+- (id<PSMTabStyle>)style
+{
+ return style;
+}
+
+- (NSString *)styleName
+{
+ return [style name];
+}
+
+- (void)setStyle:(id <PSMTabStyle>)newStyle
+{
+ if (style != newStyle) {
+ [style autorelease];
+ style = [newStyle retain];
+
+ // restyle add tab button
+ if (_addTabButton) {
+ NSImage *newButtonImage = [style addTabButtonImage];
+ if (newButtonImage) {
+ [_addTabButton setUsualImage:newButtonImage];
+ }
+
+ newButtonImage = [style addTabButtonPressedImage];
+ if (newButtonImage) {
+ [_addTabButton setAlternateImage:newButtonImage];
+ }
+
+ newButtonImage = [style addTabButtonRolloverImage];
+ if (newButtonImage) {
+ [_addTabButton setRolloverImage:newButtonImage];
+ }
+ }
+
+ [self update];
+ }
+}
+
+- (void)setStyleNamed:(NSString *)name
+{
+ id <PSMTabStyle> newStyle;
+/*
+ if ([name isEqualToString:@"Aqua"]) {
+ newStyle = [[PSMAquaTabStyle alloc] init];
+ } else if ([name isEqualToString:@"Unified"]) {
+ newStyle = [[PSMUnifiedTabStyle alloc] init];
+ } else if ([name isEqualToString:@"Adium"]) {
+ newStyle = [[PSMAdiumTabStyle alloc] init];
+ } else if ([name isEqualToString:@"Card"]) {
+ newStyle = [[PSMCardTabStyle alloc] init];
+ } else {
+*/
+ newStyle = [[PSMMetalTabStyle alloc] init];
+/*
+ }
+*/
+
+ [self setStyle:newStyle];
+ [newStyle release];
+}
+
+- (PSMTabBarOrientation)orientation
+{
+ return _orientation;
+}
+
+- (void)setOrientation:(PSMTabBarOrientation)value
+{
+ PSMTabBarOrientation lastOrientation = _orientation;
+ _orientation = value;
+
+ if (_tabBarWidth < 10) {
+ _tabBarWidth = 120;
+ }
+
+ if (lastOrientation != _orientation) {
+ [[self style] setOrientation:_orientation];
+
+ [self _positionOverflowMenu]; //move the overflow popup button to the right place
+ [self update:NO];
+ }
+}
+
+- (BOOL)canCloseOnlyTab
+{
+ return _canCloseOnlyTab;
+}
+
+- (void)setCanCloseOnlyTab:(BOOL)value
+{
+ _canCloseOnlyTab = value;
+ if ([_cells count] == 1) {
+ [self update];
+ }
+}
+
+- (BOOL)disableTabClose
+{
+ return _disableTabClose;
+}
+
+- (void)setDisableTabClose:(BOOL)value
+{
+ _disableTabClose = value;
+ [self update];
+}
+
+- (BOOL)hideForSingleTab
+{
+ return _hideForSingleTab;
+}
+
+- (void)setHideForSingleTab:(BOOL)value
+{
+ _hideForSingleTab = value;
+ [self update];
+}
+
+- (BOOL)showAddTabButton
+{
+ return _showAddTabButton;
+}
+
+- (void)setShowAddTabButton:(BOOL)value
+{
+ _showAddTabButton = value;
+ if (!NSIsEmptyRect([_controller addButtonRect]))
+ [_addTabButton setFrame:[_controller addButtonRect]];
+
+ [_addTabButton setHidden:!_showAddTabButton];
+ [_addTabButton setNeedsDisplay:YES];
+
+ [self update];
+}
+
+- (NSInteger)cellMinWidth
+{
+ return _cellMinWidth;
+}
+
+- (void)setCellMinWidth:(NSInteger)value
+{
+ _cellMinWidth = value;
+ [self update];
+}
+
+- (NSInteger)cellMaxWidth
+{
+ return _cellMaxWidth;
+}
+
+- (void)setCellMaxWidth:(NSInteger)value
+{
+ _cellMaxWidth = value;
+ [self update];
+}
+
+- (NSInteger)cellOptimumWidth
+{
+ return _cellOptimumWidth;
+}
+
+- (void)setCellOptimumWidth:(NSInteger)value
+{
+ _cellOptimumWidth = value;
+ [self update];
+}
+
+- (BOOL)sizeCellsToFit
+{
+ return _sizeCellsToFit;
+}
+
+- (void)setSizeCellsToFit:(BOOL)value
+{
+ _sizeCellsToFit = value;
+ [self update];
+}
+
+- (BOOL)useOverflowMenu
+{
+ return _useOverflowMenu;
+}
+
+- (void)setUseOverflowMenu:(BOOL)value
+{
+ _useOverflowMenu = value;
+ [self update];
+}
+
+- (PSMRolloverButton *)addTabButton
+{
+ return _addTabButton;
+}
+
+- (PSMOverflowPopUpButton *)overflowPopUpButton
+{
+ return _overflowPopUpButton;
+}
+
+- (BOOL)allowsBackgroundTabClosing
+{
+ return _allowsBackgroundTabClosing;
+}
+
+- (void)setAllowsBackgroundTabClosing:(BOOL)value
+{
+ _allowsBackgroundTabClosing = value;
+}
+
+- (BOOL)allowsResizing
+{
+ return _allowsResizing;
+}
+
+- (void)setAllowsResizing:(BOOL)value
+{
+ _allowsResizing = value;
+}
+
+- (BOOL)selectsTabsOnMouseDown
+{
+ return _selectsTabsOnMouseDown;
+}
+
+- (void)setSelectsTabsOnMouseDown:(BOOL)value
+{
+ _selectsTabsOnMouseDown = value;
+}
+
+- (BOOL)automaticallyAnimates
+{
+ return _automaticallyAnimates;
+}
+
+- (void)setAutomaticallyAnimates:(BOOL)value
+{
+ _automaticallyAnimates = value;
+}
+
+- (BOOL)alwaysShowActiveTab
+{
+ return _alwaysShowActiveTab;
+}
+
+- (void)setAlwaysShowActiveTab:(BOOL)value
+{
+ _alwaysShowActiveTab = value;
+}
+
+- (BOOL)allowsScrubbing
+{
+ return _allowsScrubbing;
+}
+
+- (void)setAllowsScrubbing:(BOOL)value
+{
+ _allowsScrubbing = value;
+}
+
+- (PSMTabBarTearOffStyle)tearOffStyle
+{
+ return _tearOffStyle;
+}
+
+- (void)setTearOffStyle:(PSMTabBarTearOffStyle)tearOffStyle
+{
+ _tearOffStyle = tearOffStyle;
+}
+
+#pragma mark -
+#pragma mark Functionality
+
+- (void)addTabViewItem:(NSTabViewItem *)item
+{
+ // create cell
+ PSMTabBarCell *cell = [[PSMTabBarCell alloc] initWithControlView:self];
+ NSRect cellRect, lastCellFrame = [[_cells lastObject] frame];
+
+ if ([self orientation] == PSMTabBarHorizontalOrientation) {
+ cellRect = [self genericCellRect];
+ cellRect.size.width = 30;
+ cellRect.origin.x = lastCellFrame.origin.x + lastCellFrame.size.width;
+ } else {
+ cellRect = /*lastCellFrame*/[self genericCellRect];
+ cellRect.size.width = lastCellFrame.size.width;
+ cellRect.size.height = 0;
+ cellRect.origin.y = lastCellFrame.origin.y + lastCellFrame.size.height;
+ }
+
+ [cell setRepresentedObject:item];
+ [cell setFrame:cellRect];
+
+ // bind it up
+ [self bindPropertiesForCell:cell andTabViewItem:item];
+
+ // add to collection
+ [_cells addObject:cell];
+ [cell release];
+ if ([_cells count] == [tabView numberOfTabViewItems]) {
+ [self update]; // don't update unless all are accounted for!
+ }
+}
+
+- (void)removeTabForCell:(PSMTabBarCell *)cell
+{
+ NSTabViewItem *item = [cell representedObject];
+
+ // unbind
+ [[cell indicator] unbind:@"animate"];
+ [[cell indicator] unbind:@"hidden"];
+ [cell unbind:@"hasIcon"];
+ [cell unbind:@"hasLargeImage"];
+ [cell unbind:@"title"];
+ [cell unbind:@"count"];
+ [cell unbind:@"countColor"];
+ [cell unbind:@"isEdited"];
+
+ if ([item identifier] != nil) {
+ if ([[item identifier] respondsToSelector:@selector(isProcessing)]) {
+ [[item identifier] removeObserver:cell forKeyPath:@"isProcessing"];
+ }
+ }
+
+ if ([item identifier] != nil) {
+ if ([[item identifier] respondsToSelector:@selector(icon)]) {
+ [[item identifier] removeObserver:cell forKeyPath:@"icon"];
+ }
+ }
+
+ if ([item identifier] != nil) {
+ if ([[item identifier] respondsToSelector:@selector(objectCount)]) {
+ [[item identifier] removeObserver:cell forKeyPath:@"objectCount"];
+ }
+ }
+
+ if ([item identifier] != nil) {
+ if ([[item identifier] respondsToSelector:@selector(countColor)]) {
+ [[item identifier] removeObserver:cell forKeyPath:@"countColor"];
+ }
+ }
+
+ if ([item identifier] != nil) {
+ if ([[item identifier] respondsToSelector:@selector(largeImage)]) {
+ [[item identifier] removeObserver:cell forKeyPath:@"largeImage"];
+ }
+ }
+
+ if ([item identifier] != nil) {
+ if ([[item identifier] respondsToSelector:@selector(isEdited)]) {
+ [[item identifier] removeObserver:cell forKeyPath:@"isEdited"];
+ }
+ }
+
+ // stop watching identifier
+ [item removeObserver:self forKeyPath:@"identifier"];
+
+ // remove indicator
+ if ([[self subviews] containsObject:[cell indicator]]) {
+ [[cell indicator] removeFromSuperview];
+ }
+ // remove tracking
+ [[NSNotificationCenter defaultCenter] removeObserver:cell];
+
+ if ([cell closeButtonTrackingTag] != 0) {
+ [self removeTrackingRect:[cell closeButtonTrackingTag]];
+ [cell setCloseButtonTrackingTag:0];
+ }
+ if ([cell cellTrackingTag] != 0) {
+ [self removeTrackingRect:[cell cellTrackingTag]];
+ [cell setCellTrackingTag:0];
+ }
+
+ // pull from collection
+ [_cells removeObject:cell];
+
+ [self update];
+}
+
+- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context
+{
+ // did the tab's identifier change?
+ if ([keyPath isEqualToString:@"identifier"]) {
+ NSEnumerator *e = [_cells objectEnumerator];
+ PSMTabBarCell *cell;
+ while ( (cell = [e nextObject]) ) {
+ if ([cell representedObject] == object) {
+ [self _bindPropertiesForCell:cell andTabViewItem:object];
+ }
+ }
+ }
+}
+
+#pragma mark -
+#pragma mark Hide/Show
+
+- (void)hideTabBar:(BOOL)hide animate:(BOOL)animate
+{
+ if (!_awakenedFromNib || (_isHidden && hide) || (!_isHidden && !hide) || (_currentStep != kPSMIsNotBeingResized)) {
+ return;
+ }
+
+ [[self subviews] makeObjectsPerformSelector:@selector(removeFromSuperview)];
+
+ _isHidden = hide;
+ _currentStep = 0;
+ if (!animate) {
+ _currentStep = (NSInteger)kPSMHideAnimationSteps;
+ }
+
+ if (hide) {
+ [_overflowPopUpButton removeFromSuperview];
+ [_addTabButton removeFromSuperview];
+ } else if (!animate) {
+ [self addSubview:_overflowPopUpButton];
+ [self addSubview:_addTabButton];
+ }
+
+ CGFloat partnerOriginalSize, partnerOriginalOrigin, myOriginalSize, myOriginalOrigin, partnerTargetSize, partnerTargetOrigin, myTargetSize, myTargetOrigin;
+
+ // target values for partner
+ if ([self orientation] == PSMTabBarHorizontalOrientation) {
+ // current (original) values
+ myOriginalSize = [self frame].size.height;
+ myOriginalOrigin = [self frame].origin.y;
+ if (partnerView) {
+ partnerOriginalSize = [partnerView frame].size.height;
+ partnerOriginalOrigin = [partnerView frame].origin.y;
+ } else {
+ partnerOriginalSize = [[self window] frame].size.height;
+ partnerOriginalOrigin = [[self window] frame].origin.y;
+ }
+
+ if (partnerView) {
+ // above or below me?
+ if ((myOriginalOrigin - 22) > partnerOriginalOrigin) {
+ // partner is below me
+ if (_isHidden) {
+ // I'm shrinking
+ myTargetOrigin = myOriginalOrigin + 21;
+ myTargetSize = myOriginalSize - 21;
+ partnerTargetOrigin = partnerOriginalOrigin;
+ partnerTargetSize = partnerOriginalSize + 21;
+ } else {
+ // I'm growing
+ myTargetOrigin = myOriginalOrigin - 21;
+ myTargetSize = myOriginalSize + 21;
+ partnerTargetOrigin = partnerOriginalOrigin;
+ partnerTargetSize = partnerOriginalSize - 21;
+ }
+ } else {
+ // partner is above me
+ if (_isHidden) {
+ // I'm shrinking
+ myTargetOrigin = myOriginalOrigin;
+ myTargetSize = myOriginalSize - 21;
+ partnerTargetOrigin = partnerOriginalOrigin - 21;
+ partnerTargetSize = partnerOriginalSize + 21;
+ } else {
+ // I'm growing
+ myTargetOrigin = myOriginalOrigin;
+ myTargetSize = myOriginalSize + 21;
+ partnerTargetOrigin = partnerOriginalOrigin + 21;
+ partnerTargetSize = partnerOriginalSize - 21;
+ }
+ }
+ } else {
+ // for window movement
+ if (_isHidden) {
+ // I'm shrinking
+ myTargetOrigin = myOriginalOrigin;
+ myTargetSize = myOriginalSize - 21;
+ partnerTargetOrigin = partnerOriginalOrigin + 21;
+ partnerTargetSize = partnerOriginalSize - 21;
+ } else {
+ // I'm growing
+ myTargetOrigin = myOriginalOrigin;
+ myTargetSize = myOriginalSize + 21;
+ partnerTargetOrigin = partnerOriginalOrigin - 21;
+ partnerTargetSize = partnerOriginalSize + 21;
+ }
+ }
+ } else /* vertical */ {
+ // current (original) values
+ myOriginalSize = [self frame].size.width;
+ myOriginalOrigin = [self frame].origin.x;
+ if (partnerView) {
+ partnerOriginalSize = [partnerView frame].size.width;
+ partnerOriginalOrigin = [partnerView frame].origin.x;
+ } else {
+ partnerOriginalSize = [[self window] frame].size.width;
+ partnerOriginalOrigin = [[self window] frame].origin.x;
+ }
+
+ if (partnerView) {
+ //to the left or right?
+ if (myOriginalOrigin < partnerOriginalOrigin + partnerOriginalSize) {
+ // partner is to the left
+ if (_isHidden) {
+ // I'm shrinking
+ myTargetOrigin = myOriginalOrigin;
+ myTargetSize = 1;
+ partnerTargetOrigin = partnerOriginalOrigin - myOriginalSize + 1;
+ partnerTargetSize = partnerOriginalSize + myOriginalSize - 1;
+ _tabBarWidth = myOriginalSize;
+ } else {
+ // I'm growing
+ myTargetOrigin = myOriginalOrigin;
+ myTargetSize = myOriginalSize + _tabBarWidth;
+ partnerTargetOrigin = partnerOriginalOrigin + _tabBarWidth;
+ partnerTargetSize = partnerOriginalSize - _tabBarWidth;
+ }
+ } else {
+ // partner is to the right
+ if (_isHidden) {
+ // I'm shrinking
+ myTargetOrigin = myOriginalOrigin + myOriginalSize;
+ myTargetSize = 1;
+ partnerTargetOrigin = partnerOriginalOrigin;
+ partnerTargetSize = partnerOriginalSize + myOriginalSize;
+ _tabBarWidth = myOriginalSize;
+ } else {
+ // I'm growing
+ myTargetOrigin = myOriginalOrigin - _tabBarWidth;
+ myTargetSize = myOriginalSize + _tabBarWidth;
+ partnerTargetOrigin = partnerOriginalOrigin;
+ partnerTargetSize = partnerOriginalSize - _tabBarWidth;
+ }
+ }
+ } else {
+ // for window movement
+ if (_isHidden) {
+ // I'm shrinking
+ myTargetOrigin = myOriginalOrigin;
+ myTargetSize = 1;
+ partnerTargetOrigin = partnerOriginalOrigin + myOriginalSize - 1;
+ partnerTargetSize = partnerOriginalSize - myOriginalSize + 1;
+ _tabBarWidth = myOriginalSize;
+ } else {
+ // I'm growing
+ myTargetOrigin = myOriginalOrigin;
+ myTargetSize = _tabBarWidth;
+ partnerTargetOrigin = partnerOriginalOrigin - _tabBarWidth + 1;
+ partnerTargetSize = partnerOriginalSize + _tabBarWidth - 1;
+ }
+ }
+
+ if (!_isHidden && [[self delegate] respondsToSelector:@selector(desiredWidthForVerticalTabBar:)])
+ myTargetSize = [[self delegate] desiredWidthForVerticalTabBar:self];
+ }
+
+ NSDictionary *userInfo = [NSDictionary dictionaryWithObjectsAndKeys:[NSNumber numberWithDouble:myOriginalOrigin], @"myOriginalOrigin", [NSNumber numberWithDouble:partnerOriginalOrigin], @"partnerOriginalOrigin", [NSNumber numberWithDouble:myOriginalSize], @"myOriginalSize", [NSNumber numberWithDouble:partnerOriginalSize], @"partnerOriginalSize", [NSNumber numberWithDouble:myTargetOrigin], @"myTargetOrigin", [NSNumber numberWithDouble:partnerTargetOrigin], @"partnerTargetOrigin", [NSNumber numberWithDouble:myTargetSize], @"myTargetSize", [NSNumber numberWithDouble:partnerTargetSize], @"partnerTargetSize", nil];
+ if (_showHideAnimationTimer) {
+ [_showHideAnimationTimer invalidate];
+ [_showHideAnimationTimer release];
+ }
+ _showHideAnimationTimer = [[NSTimer scheduledTimerWithTimeInterval:(1.0 / 30.0) target:self selector:@selector(animateShowHide:) userInfo:userInfo repeats:YES] retain];
+}
+
+- (void)animateShowHide:(NSTimer *)timer
+{
+ // moves the frame of the tab bar and window (or partner view) linearly to hide or show the tab bar
+ NSRect myFrame = [self frame];
+ NSDictionary *userInfo = [timer userInfo];
+ CGFloat myCurrentOrigin = ([[userInfo objectForKey:@"myOriginalOrigin"] doubleValue] + (([[userInfo objectForKey:@"myTargetOrigin"] doubleValue] - [[userInfo objectForKey:@"myOriginalOrigin"] doubleValue]) * (_currentStep/kPSMHideAnimationSteps)));
+ CGFloat myCurrentSize = ([[userInfo objectForKey:@"myOriginalSize"] doubleValue] + (([[userInfo objectForKey:@"myTargetSize"] doubleValue] - [[userInfo objectForKey:@"myOriginalSize"] doubleValue]) * (_currentStep/kPSMHideAnimationSteps)));
+ CGFloat partnerCurrentOrigin = ([[userInfo objectForKey:@"partnerOriginalOrigin"] doubleValue] + (([[userInfo objectForKey:@"partnerTargetOrigin"] doubleValue] - [[userInfo objectForKey:@"partnerOriginalOrigin"] doubleValue]) * (_currentStep/kPSMHideAnimationSteps)));
+ CGFloat partnerCurrentSize = ([[userInfo objectForKey:@"partnerOriginalSize"] doubleValue] + (([[userInfo objectForKey:@"partnerTargetSize"] doubleValue] - [[userInfo objectForKey:@"partnerOriginalSize"] doubleValue]) * (_currentStep/kPSMHideAnimationSteps)));
+
+ NSRect myNewFrame;
+ if ([self orientation] == PSMTabBarHorizontalOrientation) {
+ myNewFrame = NSMakeRect(myFrame.origin.x, myCurrentOrigin, myFrame.size.width, myCurrentSize);
+ } else {
+ myNewFrame = NSMakeRect(myCurrentOrigin, myFrame.origin.y, myCurrentSize, myFrame.size.height);
+ }
+
+ if (partnerView) {
+ // resize self and view
+ NSRect resizeRect;
+ if ([self orientation] == PSMTabBarHorizontalOrientation) {
+ resizeRect = NSMakeRect([partnerView frame].origin.x, partnerCurrentOrigin, [partnerView frame].size.width, partnerCurrentSize);
+ } else {
+ resizeRect = NSMakeRect(partnerCurrentOrigin, [partnerView frame].origin.y, partnerCurrentSize, [partnerView frame].size.height);
+ }
+ [partnerView setFrame:resizeRect];
+ [partnerView setNeedsDisplay:YES];
+ [self setFrame:myNewFrame];
+ } else {
+ // resize self and window
+ NSRect resizeRect;
+ if ([self orientation] == PSMTabBarHorizontalOrientation) {
+ resizeRect = NSMakeRect([[self window] frame].origin.x, partnerCurrentOrigin, [[self window] frame].size.width, partnerCurrentSize);
+ } else {
+ resizeRect = NSMakeRect(partnerCurrentOrigin, [[self window] frame].origin.y, partnerCurrentSize, [[self window] frame].size.height);
+ }
+ [[self window] setFrame:resizeRect display:YES];
+ [self setFrame:myNewFrame];
+ }
+
+ // next
+ _currentStep++;
+ if (_currentStep == kPSMHideAnimationSteps + 1) {
+ _currentStep = kPSMIsNotBeingResized;
+ [self viewDidEndLiveResize];
+ [self update:NO];
+
+ //send the delegate messages
+ if (_isHidden) {
+ if ([[self delegate] respondsToSelector:@selector(tabView:tabBarDidHide:)]) {
+ [[self delegate] tabView:[self tabView] tabBarDidHide:self];
+ }
+ } else {
+ [self addSubview:_overflowPopUpButton];
+ [self addSubview:_addTabButton];
+
+ if ([[self delegate] respondsToSelector:@selector(tabView:tabBarDidUnhide:)]) {
+ [[self delegate] tabView:[self tabView] tabBarDidUnhide:self];
+ }
+ }
+
+ [_showHideAnimationTimer invalidate];
+ [_showHideAnimationTimer release]; _showHideAnimationTimer = nil;
+ }
+ [[self window] display];
+}
+
+- (BOOL)isTabBarHidden
+{
+ return _isHidden;
+}
+
+- (BOOL)isAnimating
+{
+ return _animationTimer != nil;
+}
+
+- (id)partnerView
+{
+ return partnerView;
+}
+
+- (void)setPartnerView:(id)view
+{
+ [partnerView release];
+ [view retain];
+ partnerView = view;
+}
+
+#pragma mark -
+#pragma mark Drawing
+
+- (BOOL)isFlipped
+{
+ return YES;
+}
+
+- (void)drawRect:(NSRect)rect
+{
+ [style drawTabBar:self inRect:rect];
+}
+
+- (void)update
+{
+ [self update:_automaticallyAnimates];
+}
+
+- (void)update:(BOOL)animate
+{
+ // make sure all of our tabs are accounted for before updating
+ if ([[self tabView] numberOfTabViewItems] != [_cells count]) {
+ return;
+ }
+
+ // hide/show? (these return if already in desired state)
+ if ( (_hideForSingleTab) && ([_cells count] <= 1) ) {
+ [self hideTabBar:YES animate:YES];
+ return;
+ } else {
+ [self hideTabBar:NO animate:YES];
+ }
+
+ [self removeAllToolTips];
+ [_controller layoutCells]; //eventually we should only have to call this when we know something has changed
+
+ PSMTabBarCell *currentCell;
+
+ NSMenu *overflowMenu = [_controller overflowMenu];
+ [_overflowPopUpButton setHidden:(overflowMenu == nil)];
+ [_overflowPopUpButton setMenu:overflowMenu];
+
+ if (_animationTimer) {
+ [_animationTimer invalidate];
+ [_animationTimer release]; _animationTimer = nil;
+ }
+
+ if (animate) {
+ NSMutableArray *targetFrames = [NSMutableArray arrayWithCapacity:[_cells count]];
+
+ for (NSInteger i = 0; i < [_cells count]; i++) {
+ currentCell = [_cells objectAtIndex:i];
+
+ //we're going from NSRect -> NSValue -> NSRect -> NSValue here - oh well
+ [targetFrames addObject:[NSValue valueWithRect:[_controller cellFrameAtIndex:i]]];
+ }
+
+ [_addTabButton setHidden:!_showAddTabButton];
+
+ NSAnimation *animation = [[NSAnimation alloc] initWithDuration:0.50 animationCurve:NSAnimationEaseInOut];
+ [animation setAnimationBlockingMode:NSAnimationNonblocking];
+ [animation startAnimation];
+ _animationTimer = [[NSTimer scheduledTimerWithTimeInterval:1.0 / 30.0
+ target:self
+ selector:@selector(_animateCells:)
+ userInfo:[NSArray arrayWithObjects:targetFrames, animation, nil]
+ repeats:YES] retain];
+ [animation release];
+ [[NSRunLoop currentRunLoop] addTimer:_animationTimer forMode:NSEventTrackingRunLoopMode];
+ [self _animateCells:_animationTimer];
+
+ } else {
+ for (NSInteger i = 0; i < [_cells count]; i++) {
+ currentCell = [_cells objectAtIndex:i];
+ [currentCell setFrame:[_controller cellFrameAtIndex:i]];
+
+ if (![currentCell isInOverflowMenu]) {
+ [self _setupTrackingRectsForCell:currentCell];
+ }
+ }
+
+ [_addTabButton setFrame:[_controller addButtonRect]];
+ [_addTabButton setHidden:!_showAddTabButton];
+ [self setNeedsDisplay:YES];
+ }
+}
+
+- (void)_animateCells:(NSTimer *)timer
+{
+ NSAnimation *animation = [[timer userInfo] objectAtIndex:1];
+ NSArray *targetFrames = [[timer userInfo] objectAtIndex:0];
+ PSMTabBarCell *currentCell;
+ NSInteger cellCount = [_cells count];
+
+ if ((cellCount > 0) && [animation isAnimating]) {
+ //compare our target position with the current position and move towards the target
+ for (NSInteger i = 0; i < [targetFrames count] && i < cellCount; i++) {
+ currentCell = [_cells objectAtIndex:i];
+ NSRect cellFrame = [currentCell frame], targetFrame = [[targetFrames objectAtIndex:i] rectValue];
+ CGFloat sizeChange;
+ CGFloat originChange;
+
+ if ([self orientation] == PSMTabBarHorizontalOrientation) {
+ sizeChange = (targetFrame.size.width - cellFrame.size.width) * [animation currentProgress];
+ originChange = (targetFrame.origin.x - cellFrame.origin.x) * [animation currentProgress];
+ cellFrame.size.width += sizeChange;
+ cellFrame.origin.x += originChange;
+ } else {
+ sizeChange = (targetFrame.size.height - cellFrame.size.height) * [animation currentProgress];
+ originChange = (targetFrame.origin.y - cellFrame.origin.y) * [animation currentProgress];
+ cellFrame.size.height += sizeChange;
+ cellFrame.origin.y += originChange;
+ }
+
+ [currentCell setFrame:cellFrame];
+
+ //highlight the cell if the mouse is over it
+ NSPoint mousePoint = [self convertPoint:[[self window] mouseLocationOutsideOfEventStream] fromView:nil];
+ NSRect closeRect = [currentCell closeButtonRectForFrame:cellFrame];
+ [currentCell setHighlighted:NSMouseInRect(mousePoint, cellFrame, [self isFlipped])];
+ [currentCell setCloseButtonOver:NSMouseInRect(mousePoint, closeRect, [self isFlipped])];
+ }
+
+ if (_showAddTabButton) {
+ //animate the add tab button
+ NSRect target = [_controller addButtonRect], frame = [_addTabButton frame];
+ frame.origin.x += (target.origin.x - frame.origin.x) * [animation currentProgress];
+ [_addTabButton setFrame:frame];
+ }
+ } else {
+ //put all the cells where they should be in their final position
+ if (cellCount > 0) {
+ for (NSInteger i = 0; i < [targetFrames count] && i < cellCount; i++) {
+ PSMTabBarCell *currentCell = [_cells objectAtIndex:i];
+ NSRect cellFrame = [currentCell frame], targetFrame = [[targetFrames objectAtIndex:i] rectValue];
+
+ if ([self orientation] == PSMTabBarHorizontalOrientation) {
+ cellFrame.size.width = targetFrame.size.width;
+ cellFrame.origin.x = targetFrame.origin.x;
+ } else {
+ cellFrame.size.height = targetFrame.size.height;
+ cellFrame.origin.y = targetFrame.origin.y;
+ }
+
+ [currentCell setFrame:cellFrame];
+
+ //highlight the cell if the mouse is over it
+ NSPoint mousePoint = [self convertPoint:[[self window] mouseLocationOutsideOfEventStream] fromView:nil];
+ NSRect closeRect = [currentCell closeButtonRectForFrame:cellFrame];
+ [currentCell setHighlighted:NSMouseInRect(mousePoint, cellFrame, [self isFlipped])];
+ [currentCell setCloseButtonOver:NSMouseInRect(mousePoint, closeRect, [self isFlipped])];
+ }
+ }
+
+ //set the frame for the add tab button
+ if (_showAddTabButton) {
+ NSRect frame = [_addTabButton frame];
+ frame.origin.x = [_controller addButtonRect].origin.x;
+ [_addTabButton setFrame:frame];
+ }
+
+ [_animationTimer invalidate];
+ [_animationTimer release]; _animationTimer = nil;
+
+ for (NSInteger i = 0; i < cellCount; i++) {
+ currentCell = [_cells objectAtIndex:i];
+
+ //we've hit the cells that are in overflow, stop setting up tracking rects
+ if ([currentCell isInOverflowMenu]) {
+ break;
+ }
+
+ [self _setupTrackingRectsForCell:currentCell];
+ }
+ }
+
+ [self setNeedsDisplay:YES];
+}
+
+- (void)_setupTrackingRectsForCell:(PSMTabBarCell *)cell
+{
+ NSInteger tag, index = [_cells indexOfObject:cell];
+ NSRect cellTrackingRect = [_controller cellTrackingRectAtIndex:index];
+ NSPoint mousePoint = [self convertPoint:[[self window] mouseLocationOutsideOfEventStream] fromView:nil];
+ BOOL mouseInCell = NSMouseInRect(mousePoint, cellTrackingRect, [self isFlipped]);
+
+ //set the cell tracking rect
+ [self removeTrackingRect:[cell cellTrackingTag]];
+ tag = [self addTrackingRect:cellTrackingRect owner:cell userData:nil assumeInside:mouseInCell];
+ [cell setCellTrackingTag:tag];
+ [cell setHighlighted:mouseInCell];
+
+ if ([cell hasCloseButton] && ![cell isCloseButtonSuppressed]) {
+ NSRect closeRect = [_controller closeButtonTrackingRectAtIndex:index];
+ BOOL mouseInCloseRect = NSMouseInRect(mousePoint, closeRect, [self isFlipped]);
+
+ //set the close button tracking rect
+ [self removeTrackingRect:[cell closeButtonTrackingTag]];
+ tag = [self addTrackingRect:closeRect owner:cell userData:nil assumeInside:mouseInCloseRect];
+ [cell setCloseButtonTrackingTag:tag];
+
+ [cell setCloseButtonOver:mouseInCloseRect];
+ }
+
+ //set the tooltip tracking rect
+ [self addToolTipRect:[cell frame] owner:self userData:nil];
+}
+
+- (void)_positionOverflowMenu
+{
+ NSRect cellRect, frame = [self frame];
+ cellRect.size.height = [style tabCellHeight];
+ cellRect.size.width = [style rightMarginForTabBarControl];
+
+ if ([self orientation] == PSMTabBarHorizontalOrientation) {
+ cellRect.origin.y = 0;
+ cellRect.origin.x = frame.size.width - [style rightMarginForTabBarControl] + (_resizeAreaCompensation ? -(_resizeAreaCompensation - 1) : 1);
+ [_overflowPopUpButton setAutoresizingMask:NSViewNotSizable | NSViewMinXMargin];
+ } else {
+ cellRect.origin.x = 0;
+ cellRect.origin.y = frame.size.height - [style tabCellHeight];
+ cellRect.size.width = frame.size.width;
+ [_overflowPopUpButton setAutoresizingMask:NSViewNotSizable | NSViewMinXMargin | NSViewMinYMargin];
+ }
+
+ [_overflowPopUpButton setFrame:cellRect];
+}
+
+- (void)_checkWindowFrame
+{
+ //figure out if the new frame puts the control in the way of the resize widget
+ NSWindow *window = [self window];
+
+ if (window) {
+ NSRect resizeWidgetFrame = [[window contentView] frame];
+ resizeWidgetFrame.origin.x += resizeWidgetFrame.size.width - 22;
+ resizeWidgetFrame.size.width = 22;
+ resizeWidgetFrame.size.height = 22;
+
+ if ([window showsResizeIndicator] && NSIntersectsRect([self frame], resizeWidgetFrame)) {
+ //the resize widgets are larger on metal windows
+ _resizeAreaCompensation = [window styleMask] & NSTexturedBackgroundWindowMask ? 20 : 8;
+ } else {
+ _resizeAreaCompensation = 0;
+ }
+
+ [self _positionOverflowMenu];
+ }
+}
+
+#pragma mark -
+#pragma mark Mouse Tracking
+
+- (BOOL)mouseDownCanMoveWindow
+{
+ return NO;
+}
+
+- (BOOL)acceptsFirstMouse:(NSEvent *)theEvent
+{
+ return YES;
+}
+
+- (void)mouseDown:(NSEvent *)theEvent
+{
+ _didDrag = NO;
+
+ // keep for dragging
+ [self setLastMouseDownEvent:theEvent];
+ // what cell?
+ NSPoint mousePt = [self convertPoint:[theEvent locationInWindow] fromView:nil];
+ NSRect frame = [self frame];
+
+ if ([self orientation] == PSMTabBarVerticalOrientation && [self allowsResizing] && partnerView && (mousePt.x > frame.size.width - 3)) {
+ _resizing = YES;
+ }
+
+ NSRect cellFrame;
+ PSMTabBarCell *cell = [self cellForPoint:mousePt cellFrame:&cellFrame];
+ if (cell) {
+ BOOL overClose = NSMouseInRect(mousePt, [cell closeButtonRectForFrame:cellFrame], [self isFlipped]);
+ if (overClose &&
+ ![self disableTabClose] &&
+ ![cell isCloseButtonSuppressed] &&
+ ([self allowsBackgroundTabClosing] || [[cell representedObject] isEqualTo:[tabView selectedTabViewItem]] || [theEvent modifierFlags] & NSCommandKeyMask)) {
+ [cell setCloseButtonOver:NO];
+ [cell setCloseButtonPressed:YES];
+ _closeClicked = YES;
+ } else {
+ [cell setCloseButtonPressed:NO];
+ if (_selectsTabsOnMouseDown) {
+ [self performSelector:@selector(tabClick:) withObject:cell];
+ }
+ }
+ [self setNeedsDisplay:YES];
+ }
+}
+
+- (void)mouseDragged:(NSEvent *)theEvent
+{
+ if ([self lastMouseDownEvent] == nil) {
+ return;
+ }
+
+ NSPoint currentPoint = [self convertPoint:[theEvent locationInWindow] fromView:nil];
+
+ if (_resizing) {
+ NSRect frame = [self frame];
+ CGFloat resizeAmount = [theEvent deltaX];
+ if ((currentPoint.x > frame.size.width && resizeAmount > 0) || (currentPoint.x < frame.size.width && resizeAmount < 0)) {
+ [[NSCursor resizeLeftRightCursor] push];
+
+ NSRect partnerFrame = [partnerView frame];
+
+ //do some bounds checking
+ if ((frame.size.width + resizeAmount > [self cellMinWidth]) && (frame.size.width + resizeAmount < [self cellMaxWidth])) {
+ frame.size.width += resizeAmount;
+ partnerFrame.size.width -= resizeAmount;
+ partnerFrame.origin.x += resizeAmount;
+
+ [self setFrame:frame];
+ [partnerView setFrame:partnerFrame];
+ [[self superview] setNeedsDisplay:YES];
+ }
+ }
+ return;
+ }
+
+ NSRect cellFrame;
+ NSPoint trackingStartPoint = [self convertPoint:[[self lastMouseDownEvent] locationInWindow] fromView:nil];
+ PSMTabBarCell *cell = [self cellForPoint:trackingStartPoint cellFrame:&cellFrame];
+ if (cell) {
+ //check to see if the close button was the target in the clicked cell
+ //highlight/unhighlight the close button as necessary
+ NSRect iconRect = [cell closeButtonRectForFrame:cellFrame];
+
+ if (_closeClicked && NSMouseInRect(trackingStartPoint, iconRect, [self isFlipped]) &&
+ ([self allowsBackgroundTabClosing] || [[cell representedObject] isEqualTo:[tabView selectedTabViewItem]])) {
+ [cell setCloseButtonPressed:NSMouseInRect(currentPoint, iconRect, [self isFlipped])];
+ [self setNeedsDisplay:YES];
+ return;
+ }
+
+ CGFloat dx = fabs(currentPoint.x - trackingStartPoint.x);
+ CGFloat dy = fabs(currentPoint.y - trackingStartPoint.y);
+ CGFloat distance = sqrt(dx * dx + dy * dy);
+
+ if (distance >= 10 && !_didDrag && ![[PSMTabDragAssistant sharedDragAssistant] isDragging] &&
+ [self delegate] && [[self delegate] respondsToSelector:@selector(tabView:shouldDragTabViewItem:fromTabBar:)] &&
+ [[self delegate] tabView:tabView shouldDragTabViewItem:[cell representedObject] fromTabBar:self]) {
+ _didDrag = YES;
+ [[PSMTabDragAssistant sharedDragAssistant] startDraggingCell:cell fromTabBar:self withMouseDownEvent:[self lastMouseDownEvent]];
+ }
+ }
+}
+
+- (void)mouseUp:(NSEvent *)theEvent
+{
+ if (_resizing) {
+ _resizing = NO;
+ [[NSCursor arrowCursor] set];
+ } else {
+ // what cell?
+ NSPoint mousePt = [self convertPoint:[theEvent locationInWindow] fromView:nil];
+ NSRect cellFrame, mouseDownCellFrame;
+ PSMTabBarCell *cell = [self cellForPoint:mousePt cellFrame:&cellFrame];
+ PSMTabBarCell *mouseDownCell = [self cellForPoint:[self convertPoint:[[self lastMouseDownEvent] locationInWindow] fromView:nil] cellFrame:&mouseDownCellFrame];
+ if (cell) {
+ NSPoint trackingStartPoint = [self convertPoint:[[self lastMouseDownEvent] locationInWindow] fromView:nil];
+ NSRect iconRect = [mouseDownCell closeButtonRectForFrame:mouseDownCellFrame];
+
+ if ((NSMouseInRect(mousePt, iconRect,[self isFlipped])) && ![self disableTabClose] && ![cell isCloseButtonSuppressed] && [mouseDownCell closeButtonPressed]) {
+ if (([[NSApp currentEvent] modifierFlags] & NSAlternateKeyMask) != 0) {
+ //If the user is holding Option, close all other tabs
+ NSEnumerator *enumerator = [[[[self cells] copy] autorelease] objectEnumerator];
+ PSMTabBarCell *otherCell;
+
+ while ((otherCell = [enumerator nextObject])) {
+ if (otherCell != cell)
+ [self performSelector:@selector(closeTabClick:) withObject:otherCell];
+ }
+
+ //Fix the close button for the clicked tab not to be pressed
+ [cell setCloseButtonPressed:NO];
+
+ } else {
+ //Otherwise, close this tab
+ [self performSelector:@selector(closeTabClick:) withObject:cell];
+ }
+
+ } else if (NSMouseInRect(mousePt, mouseDownCellFrame, [self isFlipped]) &&
+ (!NSMouseInRect(trackingStartPoint, [cell closeButtonRectForFrame:cellFrame], [self isFlipped]) || ![self allowsBackgroundTabClosing] || [self disableTabClose])) {
+ [mouseDownCell setCloseButtonPressed:NO];
+ // If -[self selectsTabsOnMouseDown] is TRUE, we already performed tabClick: on mouseDown.
+ if (![self selectsTabsOnMouseDown]) {
+ [self performSelector:@selector(tabClick:) withObject:cell];
+ }
+
+ } else {
+ [mouseDownCell setCloseButtonPressed:NO];
+ [self performSelector:@selector(tabNothing:) withObject:cell];
+ }
+ }
+
+ _closeClicked = NO;
+ }
+}
+
+- (NSMenu *)menuForEvent:(NSEvent *)event
+{
+ NSMenu *menu = nil;
+ NSTabViewItem *item = [[self cellForPoint:[self convertPoint:[event locationInWindow] fromView:nil] cellFrame:nil] representedObject];
+
+ if (item && [[self delegate] respondsToSelector:@selector(tabView:menuForTabViewItem:)]) {
+ menu = [[self delegate] tabView:tabView menuForTabViewItem:item];
+ }
+ return menu;
+}
+
+#pragma mark -
+#pragma mark Drag and Drop
+
+- (BOOL)shouldDelayWindowOrderingForEvent:(NSEvent *)theEvent
+{
+ return YES;
+}
+
+// NSDraggingSource
+- (NSUInteger)draggingSourceOperationMaskForLocal:(BOOL)isLocal
+{
+ return (isLocal ? NSDragOperationMove : NSDragOperationNone);
+}
+
+- (BOOL)ignoreModifierKeysWhileDragging
+{
+ return YES;
+}
+
+- (void)draggedImage:(NSImage *)anImage beganAt:(NSPoint)screenPoint
+{
+ [[PSMTabDragAssistant sharedDragAssistant] draggingBeganAt:screenPoint];
+}
+
+- (void)draggedImage:(NSImage *)image movedTo:(NSPoint)screenPoint
+{
+ [[PSMTabDragAssistant sharedDragAssistant] draggingMovedTo:screenPoint];
+}
+
+// NSDraggingDestination
+- (NSDragOperation)draggingEntered:(id <NSDraggingInfo>)sender
+{
+ if([[[sender draggingPasteboard] types] indexOfObject:@"PSMTabBarControlItemPBType"] != NSNotFound) {
+
+ if ([self delegate] && [[self delegate] respondsToSelector:@selector(tabView:shouldDropTabViewItem:inTabBar:)] &&
+ ![[self delegate] tabView:[[sender draggingSource] tabView] shouldDropTabViewItem:[[[PSMTabDragAssistant sharedDragAssistant] draggedCell] representedObject] inTabBar:self]) {
+ return NSDragOperationNone;
+ }
+
+ [[PSMTabDragAssistant sharedDragAssistant] draggingEnteredTabBar:self atPoint:[self convertPoint:[sender draggingLocation] fromView:nil]];
+ return NSDragOperationMove;
+ }
+
+ return NSDragOperationNone;
+}
+
+- (NSDragOperation)draggingUpdated:(id <NSDraggingInfo>)sender
+{
+ PSMTabBarCell *cell = [self cellForPoint:[self convertPoint:[sender draggingLocation] fromView:nil] cellFrame:nil];
+
+ if ([[[sender draggingPasteboard] types] indexOfObject:@"PSMTabBarControlItemPBType"] != NSNotFound) {
+
+ if ([self delegate] && [[self delegate] respondsToSelector:@selector(tabView:shouldDropTabViewItem:inTabBar:)] &&
+ ![[self delegate] tabView:[[sender draggingSource] tabView] shouldDropTabViewItem:[[[PSMTabDragAssistant sharedDragAssistant] draggedCell] representedObject] inTabBar:self]) {
+ return NSDragOperationNone;
+ }
+
+ [[PSMTabDragAssistant sharedDragAssistant] draggingUpdatedInTabBar:self atPoint:[self convertPoint:[sender draggingLocation] fromView:nil]];
+ return NSDragOperationMove;
+ } else if (cell) {
+ //something that was accepted by the delegate was dragged on
+
+ //Test for the space bar (the skip-the-delay key).
+ enum { virtualKeycodeForSpace = 49 }; //Source: IM:Tx (Fig. C-2)
+ union {
+ KeyMap keymap;
+ char bits[16];
+ } keymap;
+ GetKeys(keymap.keymap);
+ if ((GetCurrentEventKeyModifiers() == 0) && bit_test(keymap.bits, virtualKeycodeForSpace)) {
+ //The user pressed the space bar. This skips the delay; the user wants to pop the spring on this tab *now*.
+
+ //For some reason, it crashes if I call -fire here. I don't know why. It doesn't crash if I simply set the fire date to now.
+ [_springTimer setFireDate:[NSDate date]];
+ } else {
+ //Wind the spring for a spring-loaded drop.
+ //The delay time comes from Finder's defaults, which specifies it in milliseconds.
+ //If the delegate can't handle our spring-loaded drop, we'll abort it when the timer fires. See fireSpring:. This is simpler than constantly (checking for spring-loaded awareness and tearing down/rebuilding the timer) at every delegate change.
+
+ //If the user has dragged to a different tab, reset the timer.
+ if (_tabViewItemWithSpring != [cell representedObject]) {
+ [_springTimer invalidate];
+ [_springTimer release]; _springTimer = nil;
+ _tabViewItemWithSpring = [cell representedObject];
+ }
+ if (!_springTimer) {
+ //Finder's default delay time, as of Tiger, is 668 ms. If the user has never changed it, there's no setting in its defaults, so we default to that amount.
+ NSNumber *delayNumber = [(NSNumber *)CFPreferencesCopyAppValue((CFStringRef)@"SpringingDelayMilliseconds", (CFStringRef)@"com.apple.finder") autorelease];
+ NSTimeInterval delaySeconds = delayNumber ? [delayNumber doubleValue] / 1000.0 : 0.668;
+ _springTimer = [[NSTimer scheduledTimerWithTimeInterval:delaySeconds
+ target:self
+ selector:@selector(fireSpring:)
+ userInfo:sender
+ repeats:NO] retain];
+ }
+ }
+ return NSDragOperationCopy;
+ }
+
+ return NSDragOperationNone;
+}
+
+- (void)draggingExited:(id <NSDraggingInfo>)sender
+{
+ [_springTimer invalidate];
+ [_springTimer release]; _springTimer = nil;
+
+ [[PSMTabDragAssistant sharedDragAssistant] draggingExitedTabBar:self];
+}
+
+- (BOOL)prepareForDragOperation:(id <NSDraggingInfo>)sender
+{
+ //validate the drag operation only if there's a valid tab bar to drop into
+ return [[[sender draggingPasteboard] types] indexOfObject:@"PSMTabBarControlItemPBType"] == NSNotFound ||
+ [[PSMTabDragAssistant sharedDragAssistant] destinationTabBar] != nil;
+}
+
+- (BOOL)performDragOperation:(id <NSDraggingInfo>)sender
+{
+ if ([[[sender draggingPasteboard] types] indexOfObject:@"PSMTabBarControlItemPBType"] != NSNotFound) {
+ [[PSMTabDragAssistant sharedDragAssistant] performDragOperation];
+ } else if ([self delegate] && [[self delegate] respondsToSelector:@selector(tabView:acceptedDraggingInfo:onTabViewItem:)]) {
+ //forward the drop to the delegate
+ [[self delegate] tabView:tabView acceptedDraggingInfo:sender onTabViewItem:[[self cellForPoint:[self convertPoint:[sender draggingLocation] fromView:nil] cellFrame:nil] representedObject]];
+ }
+ return YES;
+}
+
+- (void)draggedImage:(NSImage *)anImage endedAt:(NSPoint)aPoint operation:(NSDragOperation)operation
+{
+ [[PSMTabDragAssistant sharedDragAssistant] draggedImageEndedAt:aPoint operation:operation];
+}
+
+- (void)concludeDragOperation:(id <NSDraggingInfo>)sender
+{
+
+}
+
+#pragma mark -
+#pragma mark Spring-loading
+
+- (void)fireSpring:(NSTimer *)timer
+{
+ NSAssert1(timer == _springTimer, @"Spring fired by unrecognized timer %@", timer);
+
+ id <NSDraggingInfo> sender = [timer userInfo];
+ PSMTabBarCell *cell = [self cellForPoint:[self convertPoint:[sender draggingLocation] fromView:nil] cellFrame:nil];
+ [tabView selectTabViewItem:[cell representedObject]];
+
+ _tabViewItemWithSpring = nil;
+ [_springTimer invalidate];
+ [_springTimer release]; _springTimer = nil;
+}
+
+#pragma mark -
+#pragma mark Actions
+
+- (void)overflowMenuAction:(id)sender
+{
+ NSTabViewItem *tabViewItem = (NSTabViewItem *)[sender representedObject];
+ [tabView selectTabViewItem:tabViewItem];
+}
+
+- (void)closeTabClick:(id)sender
+{
+ NSTabViewItem *item = [sender representedObject];
+ [sender retain];
+ if(([_cells count] == 1) && (![self canCloseOnlyTab]))
+ return;
+
+ if ([[self delegate] respondsToSelector:@selector(tabView:shouldCloseTabViewItem:)]) {
+ if (![[self delegate] tabView:tabView shouldCloseTabViewItem:item]) {
+ // fix mouse downed close button
+ [sender setCloseButtonPressed:NO];
+ [sender release];
+ return;
+ }
+ }
+
+ [item retain];
+
+ [tabView removeTabViewItem:item];
+ [item release];
+ [sender release];
+}
+
+- (void)tabClick:(id)sender
+{
+ [tabView selectTabViewItem:[sender representedObject]];
+}
+
+- (void)tabNothing:(id)sender
+{
+ //[self update]; // takes care of highlighting based on state
+}
+
+- (void)frameDidChange:(NSNotification *)notification
+{
+ [self _checkWindowFrame];
+
+ // trying to address the drawing artifacts for the progress indicators - hackery follows
+ // this one fixes the "blanking" effect when the control hides and shows itself
+ NSEnumerator *e = [_cells objectEnumerator];
+ PSMTabBarCell *cell;
+ while ( (cell = [e nextObject]) ) {
+ [[cell indicator] stopAnimation:self];
+
+ [[cell indicator] performSelector:@selector(startAnimation:)
+ withObject:nil
+ afterDelay:0];
+ }
+
+ [self update:NO];
+}
+
+- (void)viewDidMoveToWindow
+{
+ [self _checkWindowFrame];
+}
+
+- (void)viewWillStartLiveResize
+{
+ NSEnumerator *e = [_cells objectEnumerator];
+ PSMTabBarCell *cell;
+ while ( (cell = [e nextObject]) ) {
+ [[cell indicator] stopAnimation:self];
+ }
+ [self setNeedsDisplay:YES];
+}
+
+-(void)viewDidEndLiveResize
+{
+ NSEnumerator *e = [_cells objectEnumerator];
+ PSMTabBarCell *cell;
+ while ( (cell = [e nextObject]) ) {
+ [[cell indicator] startAnimation:self];
+ }
+
+ [self _checkWindowFrame];
+ [self update:NO];
+}
+
+- (void)resetCursorRects
+{
+ [super resetCursorRects];
+ if ([self orientation] == PSMTabBarVerticalOrientation) {
+ NSRect frame = [self frame];
+ [self addCursorRect:NSMakeRect(frame.size.width - 2, 0, 2, frame.size.height) cursor:[NSCursor resizeLeftRightCursor]];
+ }
+}
+
+- (void)windowDidMove:(NSNotification *)aNotification
+{
+ [self setNeedsDisplay:YES];
+}
+
+- (void)windowDidUpdate:(NSNotification *)notification
+{
+ // hide? must readjust things if I'm not supposed to be showing
+ // this block of code only runs when the app launches
+ if ([self hideForSingleTab] && ([_cells count] <= 1) && !_awakenedFromNib) {
+ // must adjust frames now before display
+ NSRect myFrame = [self frame];
+ if ([self orientation] == PSMTabBarHorizontalOrientation) {
+ if (partnerView) {
+ NSRect partnerFrame = [partnerView frame];
+ // above or below me?
+ if (myFrame.origin.y - 22 > [partnerView frame].origin.y) {
+ // partner is below me
+ [self setFrame:NSMakeRect(myFrame.origin.x, myFrame.origin.y + 21, myFrame.size.width, myFrame.size.height - 21)];
+ [partnerView setFrame:NSMakeRect(partnerFrame.origin.x, partnerFrame.origin.y, partnerFrame.size.width, partnerFrame.size.height + 21)];
+ } else {
+ // partner is above me
+ [self setFrame:NSMakeRect(myFrame.origin.x, myFrame.origin.y, myFrame.size.width, myFrame.size.height - 21)];
+ [partnerView setFrame:NSMakeRect(partnerFrame.origin.x, partnerFrame.origin.y - 21, partnerFrame.size.width, partnerFrame.size.height + 21)];
+ }
+ [partnerView setNeedsDisplay:YES];
+ [self setNeedsDisplay:YES];
+ } else {
+ // for window movement
+ NSRect windowFrame = [[self window] frame];
+ [[self window] setFrame:NSMakeRect(windowFrame.origin.x, windowFrame.origin.y + 21, windowFrame.size.width, windowFrame.size.height - 21) display:YES];
+ [self setFrame:NSMakeRect(myFrame.origin.x, myFrame.origin.y, myFrame.size.width, myFrame.size.height - 21)];
+ }
+ } else {
+ if (partnerView) {
+ NSRect partnerFrame = [partnerView frame];
+ //to the left or right?
+ if (myFrame.origin.x < [partnerView frame].origin.x) {
+ // partner is to the left
+ [self setFrame:NSMakeRect(myFrame.origin.x, myFrame.origin.y, 1, myFrame.size.height)];
+ [partnerView setFrame:NSMakeRect(partnerFrame.origin.x - myFrame.size.width + 1, partnerFrame.origin.y, partnerFrame.size.width + myFrame.size.width - 1, partnerFrame.size.height)];
+ } else {
+ // partner to the right
+ [self setFrame:NSMakeRect(myFrame.origin.x + myFrame.size.width, myFrame.origin.y, 1, myFrame.size.height)];
+ [partnerView setFrame:NSMakeRect(partnerFrame.origin.x, partnerFrame.origin.y, partnerFrame.size.width + myFrame.size.width, partnerFrame.size.height)];
+ }
+ _tabBarWidth = myFrame.size.width;
+ [partnerView setNeedsDisplay:YES];
+ [self setNeedsDisplay:YES];
+ } else {
+ // for window movement
+ NSRect windowFrame = [[self window] frame];
+ [[self window] setFrame:NSMakeRect(windowFrame.origin.x + myFrame.size.width - 1, windowFrame.origin.y, windowFrame.size.width - myFrame.size.width + 1, windowFrame.size.height) display:YES];
+ [self setFrame:NSMakeRect(myFrame.origin.x, myFrame.origin.y, 1, myFrame.size.height)];
+ }
+ }
+
+ _isHidden = YES;
+
+ if ([[self delegate] respondsToSelector:@selector(tabView:tabBarDidHide:)]) {
+ [[self delegate] tabView:[self tabView] tabBarDidHide:self];
+ }
+ }
+
+ _awakenedFromNib = YES;
+ [self setNeedsDisplay:YES];
+
+ //we only need to do this once
+ [[NSNotificationCenter defaultCenter] removeObserver:self name:NSWindowDidUpdateNotification object:nil];
+}
+
+#pragma mark -
+#pragma mark Menu Validation
+
+- (BOOL)validateMenuItem:(NSMenuItem *)sender
+{
+ [sender setState:([[sender representedObject] isEqualTo:[tabView selectedTabViewItem]]) ? NSOnState : NSOffState];
+
+ return [[self delegate] respondsToSelector:@selector(tabView:validateOverflowMenuItem:forTabViewItem:)] ?
+ [[self delegate] tabView:[self tabView] validateOverflowMenuItem:sender forTabViewItem:[sender representedObject]] : YES;
+}
+
+#pragma mark -
+#pragma mark NSTabView Delegate
+
+- (void)tabView:(NSTabView *)aTabView didSelectTabViewItem:(NSTabViewItem *)tabViewItem
+{
+ // here's a weird one - this message is sent before the "tabViewDidChangeNumberOfTabViewItems"
+ // message, thus I can end up updating when there are no cells, if no tabs were (yet) present
+ NSInteger tabIndex = [aTabView indexOfTabViewItem:tabViewItem];
+
+ if ([_cells count] > 0 && tabIndex < [_cells count]) {
+ PSMTabBarCell *thisCell = [_cells objectAtIndex:tabIndex];
+ if (_alwaysShowActiveTab && [thisCell isInOverflowMenu]) {
+
+ //temporarily disable the delegate in order to move the tab to a different index
+ id tempDelegate = [aTabView delegate];
+ [aTabView setDelegate:nil];
+
+ // move it all around first
+ [tabViewItem retain];
+ [thisCell retain];
+ [aTabView removeTabViewItem:tabViewItem];
+ [aTabView insertTabViewItem:tabViewItem atIndex:0];
+ [_cells removeObjectAtIndex:tabIndex];
+ [_cells insertObject:thisCell atIndex:0];
+ [thisCell setIsInOverflowMenu:NO]; //very important else we get a fun recursive loop going
+ [[_cells objectAtIndex:[_cells count] - 1] setIsInOverflowMenu:YES]; //these 2 lines are pretty uncool and this logic needs to be updated
+ [thisCell release];
+ [tabViewItem release];
+
+ [aTabView setDelegate:tempDelegate];
+
+ //reset the selection since removing it changed the selection
+ [aTabView selectTabViewItem:tabViewItem];
+
+ [self update];
+ } else {
+ [_controller setSelectedCell:thisCell];
+ [self setNeedsDisplay:YES];
+ }
+ }
+
+ if ([[self delegate] respondsToSelector:@selector(tabView:didSelectTabViewItem:)]) {
+ [[self delegate] performSelector:@selector(tabView:didSelectTabViewItem:) withObject:aTabView withObject:tabViewItem];
+ }
+}
+
+- (BOOL)tabView:(NSTabView *)aTabView shouldSelectTabViewItem:(NSTabViewItem *)tabViewItem
+{
+ if ([[self delegate] respondsToSelector:@selector(tabView:shouldSelectTabViewItem:)]) {
+ return [[self delegate] tabView:aTabView shouldSelectTabViewItem:tabViewItem];
+ } else {
+ return YES;
+ }
+}
+- (void)tabView:(NSTabView *)aTabView willSelectTabViewItem:(NSTabViewItem *)tabViewItem
+{
+ if ([[self delegate] respondsToSelector:@selector(tabView:willSelectTabViewItem:)]) {
+ [[self delegate] performSelector:@selector(tabView:willSelectTabViewItem:) withObject:aTabView withObject:tabViewItem];
+ }
+}
+
+- (void)tabViewDidChangeNumberOfTabViewItems:(NSTabView *)aTabView
+{
+ NSArray *tabItems = [tabView tabViewItems];
+ // go through cells, remove any whose representedObjects are not in [tabView tabViewItems]
+ NSEnumerator *e = [[[_cells copy] autorelease] objectEnumerator];
+ PSMTabBarCell *cell;
+ while ( (cell = [e nextObject]) ) {
+ //remove the observer binding
+ if ([cell representedObject] && ![tabItems containsObject:[cell representedObject]]) {
+ if ([[self delegate] respondsToSelector:@selector(tabView:didCloseTabViewItem:)]) {
+ [[self delegate] tabView:aTabView didCloseTabViewItem:[cell representedObject]];
+ }
+
+ [self removeTabForCell:cell];
+ }
+ }
+
+ // go through tab view items, add cell for any not present
+ NSMutableArray *cellItems = [self representedTabViewItems];
+ NSEnumerator *ex = [tabItems objectEnumerator];
+ NSTabViewItem *item;
+ while ( (item = [ex nextObject]) ) {
+ if (![cellItems containsObject:item]) {
+ [self addTabViewItem:item];
+ }
+ }
+
+ // pass along for other delegate responses
+ if ([[self delegate] respondsToSelector:@selector(tabViewDidChangeNumberOfTabViewItems:)]) {
+ [[self delegate] performSelector:@selector(tabViewDidChangeNumberOfTabViewItems:) withObject:aTabView];
+ }
+
+ // reset cursor tracking for the add tab button if one exists
+ if ([self addTabButton]) [[self addTabButton] resetCursorRects];
+}
+
+#pragma mark -
+#pragma mark Tooltips
+
+- (NSString *)view:(NSView *)view stringForToolTip:(NSToolTipTag)tag point:(NSPoint)point userData:(void *)userData
+{
+ if ([[self delegate] respondsToSelector:@selector(tabView:toolTipForTabViewItem:)]) {
+ return [[self delegate] tabView:[self tabView] toolTipForTabViewItem:[[self cellForPoint:point cellFrame:nil] representedObject]];
+ }
+ return nil;
+}
+
+#pragma mark -
+#pragma mark Archiving
+
+- (void)encodeWithCoder:(NSCoder *)aCoder
+{
+ [super encodeWithCoder:aCoder];
+ if ([aCoder allowsKeyedCoding]) {
+ [aCoder encodeObject:_cells forKey:@"PSMcells"];
+ [aCoder encodeObject:tabView forKey:@"PSMtabView"];
+ [aCoder encodeObject:_overflowPopUpButton forKey:@"PSMoverflowPopUpButton"];
+ [aCoder encodeObject:_addTabButton forKey:@"PSMaddTabButton"];
+ [aCoder encodeObject:style forKey:@"PSMstyle"];
+ [aCoder encodeInteger:_orientation forKey:@"PSMorientation"];
+ [aCoder encodeBool:_canCloseOnlyTab forKey:@"PSMcanCloseOnlyTab"];
+ [aCoder encodeBool:_disableTabClose forKey:@"PSMdisableTabClose"];
+ [aCoder encodeBool:_hideForSingleTab forKey:@"PSMhideForSingleTab"];
+ [aCoder encodeBool:_allowsBackgroundTabClosing forKey:@"PSMallowsBackgroundTabClosing"];
+ [aCoder encodeBool:_allowsResizing forKey:@"PSMallowsResizing"];
+ [aCoder encodeBool:_selectsTabsOnMouseDown forKey:@"PSMselectsTabsOnMouseDown"];
+ [aCoder encodeBool:_showAddTabButton forKey:@"PSMshowAddTabButton"];
+ [aCoder encodeBool:_sizeCellsToFit forKey:@"PSMsizeCellsToFit"];
+ [aCoder encodeInteger:_cellMinWidth forKey:@"PSMcellMinWidth"];
+ [aCoder encodeInteger:_cellMaxWidth forKey:@"PSMcellMaxWidth"];
+ [aCoder encodeInteger:_cellOptimumWidth forKey:@"PSMcellOptimumWidth"];
+ [aCoder encodeInteger:_currentStep forKey:@"PSMcurrentStep"];
+ [aCoder encodeBool:_isHidden forKey:@"PSMisHidden"];
+ [aCoder encodeObject:partnerView forKey:@"PSMpartnerView"];
+ [aCoder encodeBool:_awakenedFromNib forKey:@"PSMawakenedFromNib"];
+ [aCoder encodeObject:_lastMouseDownEvent forKey:@"PSMlastMouseDownEvent"];
+ [aCoder encodeObject:delegate forKey:@"PSMdelegate"];
+ [aCoder encodeBool:_useOverflowMenu forKey:@"PSMuseOverflowMenu"];
+ [aCoder encodeBool:_automaticallyAnimates forKey:@"PSMautomaticallyAnimates"];
+ [aCoder encodeBool:_alwaysShowActiveTab forKey:@"PSMalwaysShowActiveTab"];
+ }
+}
+
+- (id)initWithCoder:(NSCoder *)aDecoder
+{
+ self = [super initWithCoder:aDecoder];
+ if (self) {
+
+ // Initialization
+ [self initAddedProperties];
+ [self registerForDraggedTypes:[NSArray arrayWithObjects:@"PSMTabBarControlItemPBType", nil]];
+
+ if ([aDecoder allowsKeyedCoding]) {
+ _cells = [[aDecoder decodeObjectForKey:@"PSMcells"] retain];
+ tabView = [[aDecoder decodeObjectForKey:@"PSMtabView"] retain];
+ _overflowPopUpButton = [[aDecoder decodeObjectForKey:@"PSMoverflowPopUpButton"] retain];
+ _addTabButton = [[aDecoder decodeObjectForKey:@"PSMaddTabButton"] retain];
+ style = [[aDecoder decodeObjectForKey:@"PSMstyle"] retain];
+ _orientation = (PSMTabBarOrientation)[aDecoder decodeIntegerForKey:@"PSMorientation"];
+ _canCloseOnlyTab = [aDecoder decodeBoolForKey:@"PSMcanCloseOnlyTab"];
+ _disableTabClose = [aDecoder decodeBoolForKey:@"PSMdisableTabClose"];
+ _hideForSingleTab = [aDecoder decodeBoolForKey:@"PSMhideForSingleTab"];
+ _allowsBackgroundTabClosing = [aDecoder decodeBoolForKey:@"PSMallowsBackgroundTabClosing"];
+ _allowsResizing = [aDecoder decodeBoolForKey:@"PSMallowsResizing"];
+ _selectsTabsOnMouseDown = [aDecoder decodeBoolForKey:@"PSMselectsTabsOnMouseDown"];
+ _showAddTabButton = [aDecoder decodeBoolForKey:@"PSMshowAddTabButton"];
+ _sizeCellsToFit = [aDecoder decodeBoolForKey:@"PSMsizeCellsToFit"];
+ _cellMinWidth = [aDecoder decodeIntegerForKey:@"PSMcellMinWidth"];
+ _cellMaxWidth = [aDecoder decodeIntegerForKey:@"PSMcellMaxWidth"];
+ _cellOptimumWidth = [aDecoder decodeIntegerForKey:@"PSMcellOptimumWidth"];
+ _currentStep = [aDecoder decodeIntegerForKey:@"PSMcurrentStep"];
+ _isHidden = [aDecoder decodeBoolForKey:@"PSMisHidden"];
+ partnerView = [[aDecoder decodeObjectForKey:@"PSMpartnerView"] retain];
+ _awakenedFromNib = [aDecoder decodeBoolForKey:@"PSMawakenedFromNib"];
+ _lastMouseDownEvent = [[aDecoder decodeObjectForKey:@"PSMlastMouseDownEvent"] retain];
+ _useOverflowMenu = [aDecoder decodeBoolForKey:@"PSMuseOverflowMenu"];
+ _automaticallyAnimates = [aDecoder decodeBoolForKey:@"PSMautomaticallyAnimates"];
+ _alwaysShowActiveTab = [aDecoder decodeBoolForKey:@"PSMalwaysShowActiveTab"];
+ delegate = [[aDecoder decodeObjectForKey:@"PSMdelegate"] retain];
+ }
+
+ // resize
+ [self setPostsFrameChangedNotifications:YES];
+ [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(frameDidChange:) name:NSViewFrameDidChangeNotification object:self];
+ }
+
+ [self setTarget:self];
+ return self;
+}
+
+#pragma mark -
+#pragma mark IB Palette
+
+- (NSSize)minimumFrameSizeFromKnobPosition:(NSInteger)position
+{
+ return NSMakeSize(100.0, 22.0);
+}
+
+- (NSSize)maximumFrameSizeFromKnobPosition:(NSInteger)knobPosition
+{
+ return NSMakeSize(10000.0, 22.0);
+}
+
+- (void)placeView:(NSRect)newFrame
+{
+ // this is called any time the view is resized in IB
+ [self setFrame:newFrame];
+ [self update:NO];
+}
+
+#pragma mark -
+#pragma mark Convenience
+
+- (void)bindPropertiesForCell:(PSMTabBarCell *)cell andTabViewItem:(NSTabViewItem *)item
+{
+ [self _bindPropertiesForCell:cell andTabViewItem:item];
+
+ // watch for changes in the identifier
+ [item addObserver:self forKeyPath:@"identifier" options:0 context:nil];
+}
+
+- (void)_bindPropertiesForCell:(PSMTabBarCell *)cell andTabViewItem:(NSTabViewItem *)item
+{
+ // bind the indicator to the represented object's status (if it exists)
+ [[cell indicator] setHidden:YES];
+ if ([item identifier] != nil) {
+ if ([[[cell representedObject] identifier] respondsToSelector:@selector(isProcessing)]) {
+ NSMutableDictionary *bindingOptions = [NSMutableDictionary dictionary];
+ [bindingOptions setObject:NSNegateBooleanTransformerName forKey:@"NSValueTransformerName"];
+ [[cell indicator] bind:@"animate" toObject:[item identifier] withKeyPath:@"isProcessing" options:nil];
+ [[cell indicator] bind:@"hidden" toObject:[item identifier] withKeyPath:@"isProcessing" options:bindingOptions];
+ [[item identifier] addObserver:cell forKeyPath:@"isProcessing" options:0 context:nil];
+ }
+ }
+
+ // bind for the existence of an icon
+ [cell setHasIcon:NO];
+ if ([item identifier] != nil) {
+ if ([[[cell representedObject] identifier] respondsToSelector:@selector(icon)]) {
+ NSMutableDictionary *bindingOptions = [NSMutableDictionary dictionary];
+ [bindingOptions setObject:NSIsNotNilTransformerName forKey:@"NSValueTransformerName"];
+ [cell bind:@"hasIcon" toObject:[item identifier] withKeyPath:@"icon" options:bindingOptions];
+ [[item identifier] addObserver:cell forKeyPath:@"icon" options:0 context:nil];
+ }
+ }
+
+ // bind for the existence of a counter
+ [cell setCount:0];
+ if ([item identifier] != nil) {
+ if ([[[cell representedObject] identifier] respondsToSelector:@selector(objectCount)]) {
+ [cell bind:@"count" toObject:[item identifier] withKeyPath:@"objectCount" options:nil];
+ [[item identifier] addObserver:cell forKeyPath:@"objectCount" options:0 context:nil];
+ }
+ }
+
+ // bind for the color of a counter
+ [cell setCountColor:nil];
+ if ([item identifier] != nil) {
+ if ([[[cell representedObject] identifier] respondsToSelector:@selector(countColor)]) {
+ [cell bind:@"countColor" toObject:[item identifier] withKeyPath:@"countColor" options:nil];
+ [[item identifier] addObserver:cell forKeyPath:@"countColor" options:0 context:nil];
+ }
+ }
+
+ // bind for a large image
+ [cell setHasLargeImage:NO];
+ if ([item identifier] != nil) {
+ if ([[[cell representedObject] identifier] respondsToSelector:@selector(largeImage)]) {
+ NSMutableDictionary *bindingOptions = [NSMutableDictionary dictionary];
+ [bindingOptions setObject:NSIsNotNilTransformerName forKey:@"NSValueTransformerName"];
+ [cell bind:@"hasLargeImage" toObject:[item identifier] withKeyPath:@"largeImage" options:bindingOptions];
+ [[item identifier] addObserver:cell forKeyPath:@"largeImage" options:0 context:nil];
+ }
+ }
+
+ [cell setIsEdited:NO];
+ if ([item identifier] != nil) {
+ if ([[[cell representedObject] identifier] respondsToSelector:@selector(isEdited)]) {
+ [cell bind:@"isEdited" toObject:[item identifier] withKeyPath:@"isEdited" options:nil];
+ [[item identifier] addObserver:cell forKeyPath:@"isEdited" options:0 context:nil];
+ }
+ }
+
+ // bind my string value to the label on the represented tab
+ [cell bind:@"title" toObject:item withKeyPath:@"label" options:nil];
+}
+
+- (NSMutableArray *)representedTabViewItems
+{
+ NSMutableArray *temp = [NSMutableArray arrayWithCapacity:[_cells count]];
+ NSEnumerator *e = [_cells objectEnumerator];
+ PSMTabBarCell *cell;
+ while ( (cell = [e nextObject])) {
+ if ([cell representedObject]) {
+ [temp addObject:[cell representedObject]];
+ }
+ }
+ return temp;
+}
+
+- (id)cellForPoint:(NSPoint)point cellFrame:(NSRectPointer)outFrame
+{
+ if ([self orientation] == PSMTabBarHorizontalOrientation && !NSPointInRect(point, [self genericCellRect])) {
+ return nil;
+ }
+
+ NSInteger i, cnt = [_cells count];
+ for (i = 0; i < cnt; i++) {
+ PSMTabBarCell *cell = [_cells objectAtIndex:i];
+
+ if (NSPointInRect(point, [cell frame])) {
+ if (outFrame) {
+ *outFrame = [cell frame];
+ }
+ return cell;
+ }
+ }
+ return nil;
+}
+
+- (PSMTabBarCell *)lastVisibleTab
+{
+ NSInteger i, cellCount = [_cells count];
+ for (i = 0; i < cellCount; i++) {
+ if ([[_cells objectAtIndex:i] isInOverflowMenu]) {
+ return [_cells objectAtIndex:(i - 1)];
+ }
+ }
+ return [_cells objectAtIndex:(cellCount - 1)];
+}
+
+- (NSInteger)numberOfVisibleTabs
+{
+ NSInteger i, cellCount = 0;
+ PSMTabBarCell *nextCell;
+
+ for (i = 0; i < [_cells count]; i++) {
+ nextCell = [_cells objectAtIndex:i];
+
+ if ([nextCell isInOverflowMenu]) {
+ break;
+ }
+
+ if (![nextCell isPlaceholder]) {
+ cellCount++;
+ }
+ }
+
+ return cellCount;
+}
+
+#pragma mark -
+#pragma mark Accessibility
+
+-(BOOL)accessibilityIsIgnored {
+ return NO;
+}
+
+- (id)accessibilityAttributeValue:(NSString *)attribute {
+ id attributeValue = nil;
+ if ([attribute isEqualToString: NSAccessibilityRoleAttribute]) {
+ attributeValue = NSAccessibilityGroupRole;
+ } else if ([attribute isEqualToString: NSAccessibilityChildrenAttribute]) {
+ attributeValue = NSAccessibilityUnignoredChildren(_cells);
+ } else {
+ attributeValue = [super accessibilityAttributeValue:attribute];
+ }
+ return attributeValue;
+}
+
+- (id)accessibilityHitTest:(NSPoint)point {
+ id hitTestResult = self;
+
+ NSEnumerator *enumerator = [_cells objectEnumerator];
+ PSMTabBarCell *cell = nil;
+ PSMTabBarCell *highlightedCell = nil;
+
+ while (!highlightedCell && (cell = [enumerator nextObject])) {
+ if ([cell isHighlighted]) {
+ highlightedCell = cell;
+ }
+ }
+
+ if (highlightedCell) {
+ hitTestResult = [highlightedCell accessibilityHitTest:point];
+ }
+
+ return hitTestResult;
+}
+
+@end
diff --git a/Frameworks/PSMTabBar/PSMTabBarController.h b/Frameworks/PSMTabBar/PSMTabBarController.h
new file mode 100644
index 00000000..c675b981
--- /dev/null
+++ b/Frameworks/PSMTabBar/PSMTabBarController.h
@@ -0,0 +1,33 @@
+//
+// 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 {
+ PSMTabBarControl *_control;
+ NSMutableArray *_cellTrackingRects, *_closeButtonTrackingRects;
+ NSMutableArray *_cellFrames;
+ NSRect _addButtonRect;
+ NSMenu *_overflowMenu;
+}
+
+- (id)initWithTabBarControl:(PSMTabBarControl *)control;
+
+- (NSRect)addButtonRect;
+- (NSMenu *)overflowMenu;
+- (NSRect)cellTrackingRectAtIndex:(NSInteger)index;
+- (NSRect)closeButtonTrackingRectAtIndex:(NSInteger)index;
+- (NSRect)cellFrameAtIndex:(NSInteger)index;
+
+- (void)setSelectedCell:(PSMTabBarCell *)cell;
+
+- (void)layoutCells;
+
+@end
diff --git a/Frameworks/PSMTabBar/PSMTabBarController.m b/Frameworks/PSMTabBar/PSMTabBarController.m
new file mode 100644
index 00000000..8f8631b0
--- /dev/null
+++ b/Frameworks/PSMTabBar/PSMTabBarController.m
@@ -0,0 +1,665 @@
+//
+// 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"
+
+#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;
+}
+
+- (void)dealloc
+{
+ [_cellTrackingRects release];
+ [_closeButtonTrackingRects release];
+ [_cellFrames release];
+ [super dealloc];
+}
+
+/*!
+ @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:(NSInteger)index
+{
+ NSRect rect;
+ if (index > -1 && 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:(NSInteger)index
+{
+ NSRect rect;
+ if (index > -1 && 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:(NSInteger)index
+{
+ NSRect rect;
+
+ if (index > -1 && 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]) {
+ NSInteger 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;
+
+ NSInteger q = 0;
+ for (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];
+
+ NSInteger i = 0;
+ for (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
+{
+ NSInteger i, tabState, cellCount = [cells count];
+ NSRect cellRect = [_control genericCellRect];
+ PSMTabBarCell *cell;
+ NSTabViewItem *selectedTabViewItem = [[_control tabView] selectedTabViewItem];
+ NSMenuItem *menuItem;
+
+ [_overflowMenu release], _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/Frameworks/PSMTabBar/PSMTabDragAssistant.h b/Frameworks/PSMTabBar/PSMTabDragAssistant.h
new file mode 100644
index 00000000..07703b7d
--- /dev/null
+++ b/Frameworks/PSMTabBar/PSMTabDragAssistant.h
@@ -0,0 +1,100 @@
+//
+// 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;
+ NSInteger _draggedCellIndex; // for snap back
+ BOOL _isDragging;
+
+ // Support for dragging into new windows
+ PSMTabDragWindowController *_draggedTab, *_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/Frameworks/PSMTabBar/PSMTabDragAssistant.m b/Frameworks/PSMTabBar/PSMTabDragAssistant.m
new file mode 100644
index 00000000..9c9eb2a8
--- /dev/null
+++ b/Frameworks/PSMTabBar/PSMTabDragAssistant.m
@@ -0,0 +1,872 @@
+//
+// 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"
+
+#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;
+}
+
+- (void)dealloc
+{
+ [_sourceTabBar release];
+ [_destinationTabBar release];
+ [_participatingTabBars release];
+ [_draggedCell release];
+ [_animationTimer release];
+ [_sineCurveWidths release];
+ [_targetCell release];
+ [super dealloc];
+}
+
+#pragma mark -
+#pragma mark Accessors
+
+- (PSMTabBarControl *)sourceTabBar
+{
+ return _sourceTabBar;
+}
+
+- (void)setSourceTabBar:(PSMTabBarControl *)tabBar
+{
+ [tabBar retain];
+ [_sourceTabBar release];
+ _sourceTabBar = tabBar;
+}
+
+- (PSMTabBarControl *)destinationTabBar
+{
+ return _destinationTabBar;
+}
+
+- (void)setDestinationTabBar:(PSMTabBarControl *)tabBar
+{
+ [tabBar retain];
+ [_destinationTabBar release];
+ _destinationTabBar = tabBar;
+}
+
+- (PSMTabBarCell *)draggedCell
+{
+ return _draggedCell;
+}
+
+- (void)setDraggedCell:(PSMTabBarCell *)cell
+{
+ [cell retain];
+ [_draggedCell release];
+ _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
+{
+ [cell retain];
+ [_targetCell release];
+ _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
+ [control retain];
+
+ 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:NSBorderlessWindowMask tearOffStyle:_currentTearOffStyle];
+
+ cellFrame.origin.y -= cellFrame.size.height;
+ [control dragImage:[[[NSImage alloc] initWithSize:NSMakeSize(1, 1)] autorelease] 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];
+ }
+
+ [control release];
+}
+
+- (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
+ NSInteger 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]];
+
+ NSInteger 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);
+ NSInteger index;
+ NSArray *cells = [[self sourceTabBar] cells];
+
+ //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];
+ [item retain];
+ [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 release];
+ _draggedTab = nil;
+ }
+
+ if (_draggedView) {
+ [[_draggedView window] orderOut:nil];
+ [_draggedView release];
+ _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 = NSBorderlessWindowMask;
+ }
+
+ 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 compositeToPoint:drawPoint operation:NSCompositeSourceOver];
+
+ [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] autorelease];
+ [viewImage lockFocus];
+ [tabView drawRect:[tabView bounds]];
+ [viewImage unlockFocus];
+ }
+
+ if (outMask && (*outMask | NSBorderlessWindowMask)) {
+ _dragWindowOffset.height += 22;
+ }
+
+ return viewImage;
+}
+
+- (NSImage *)_miniwindowImageOfWindow:(NSWindow *)window
+{
+ NSRect rect = [window frame];
+ NSImage *image = [[[NSImage alloc] initWithSize:rect.size] autorelease];
+ [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];
+ [animation release];
+ }
+}
+
+#pragma mark -
+#pragma mark Animation
+
+- (void)animateDrag:(NSTimer *)timer
+{
+ NSEnumerator *e = [[[_participatingTabBars copy] autorelease] 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] autorelease];
+ [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
+{
+ NSInteger i, numVisibleTabs = [control numberOfVisibleTabs];
+ for (i = 0; i < numVisibleTabs; i++) {
+ PSMTabBarCell *pc = [[[PSMTabBarCell alloc] initPlaceholderWithFrame:[[self draggedCell] frame] expanded:NO inControlView:control] autorelease];
+ [[control cells] insertObject:pc atIndex:(2 * i)];
+ }
+
+ PSMTabBarCell *pc = [[[PSMTabBarCell alloc] initPlaceholderWithFrame:[[self draggedCell] frame] expanded:NO inControlView:control] autorelease];
+ 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"] retain];
+ _destinationTabBar = [[aDecoder decodeObjectForKey:@"destinationTabBar"] retain];
+ _participatingTabBars = [[aDecoder decodeObjectForKey:@"participatingTabBars"] retain];
+ _draggedCell = [[aDecoder decodeObjectForKey:@"draggedCell"] retain];
+ _draggedCellIndex = [aDecoder decodeIntegerForKey:@"draggedCellIndex"];
+ _isDragging = [aDecoder decodeBoolForKey:@"isDragging"];
+ _animationTimer = [[aDecoder decodeObjectForKey:@"animationTimer"] retain];
+ _sineCurveWidths = [[aDecoder decodeObjectForKey:@"sineCurveWidths"] retain];
+ _currentMouseLoc = [aDecoder decodePointForKey:@"currentMouseLoc"];
+ _targetCell = [[aDecoder decodeObjectForKey:@"targetCell"] retain];
+ }
+ //}
+ return self;
+}
+
+
+@end
diff --git a/Frameworks/PSMTabBar/PSMTabDragView.h b/Frameworks/PSMTabBar/PSMTabDragView.h
new file mode 100644
index 00000000..bdf73304
--- /dev/null
+++ b/Frameworks/PSMTabBar/PSMTabDragView.h
@@ -0,0 +1,20 @@
+//
+// 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, *_alternateImage;
+ CGFloat _alpha;
+}
+- (void)setFadeValue:(CGFloat)value;
+- (NSImage *)image;
+- (void)setImage:(NSImage *)image;
+- (NSImage *)alternateImage;
+- (void)setAlternateImage:(NSImage *)image;
+@end
diff --git a/Frameworks/PSMTabBar/PSMTabDragView.m b/Frameworks/PSMTabBar/PSMTabDragView.m
new file mode 100644
index 00000000..259116ae
--- /dev/null
+++ b/Frameworks/PSMTabBar/PSMTabDragView.m
@@ -0,0 +1,68 @@
+//
+// 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)dealloc
+{
+ [_image release];
+ [_alternateImage release];
+ [super dealloc];
+}
+
+- (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:NSCompositeSourceOver fraction:primaryAlpha];
+ srcRect.size = [_alternateImage size];
+ [_alternateImage drawInRect:[self bounds] fromRect:srcRect operation:NSCompositeSourceOver fraction:alternateAlpha];
+}
+
+- (void)setFadeValue:(CGFloat)value
+{
+ _alpha = value;
+}
+
+- (NSImage *)image
+{
+ return _image;
+}
+
+- (void)setImage:(NSImage *)image
+{
+ [_image release];
+ _image = [image retain];
+}
+
+- (NSImage *)alternateImage
+{
+ return _alternateImage;
+}
+
+- (void)setAlternateImage:(NSImage *)image
+{
+ [_alternateImage release];
+ _alternateImage = [image retain];
+}
+
+@end
diff --git a/Frameworks/PSMTabBar/PSMTabDragWindow.h b/Frameworks/PSMTabBar/PSMTabDragWindow.h
new file mode 100644
index 00000000..fd26e749
--- /dev/null
+++ b/Frameworks/PSMTabBar/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/Frameworks/PSMTabBar/PSMTabDragWindow.m b/Frameworks/PSMTabBar/PSMTabDragWindow.m
new file mode 100644
index 00000000..0d07c432
--- /dev/null
+++ b/Frameworks/PSMTabBar/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] autorelease];
+}
+
+- (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)] autorelease];
+ [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 | NSBorderlessWindowMask) {
+ windowFrame.size.height += 22;
+ }
+
+ [self setFrame:windowFrame display:YES];
+ }
+ return self;
+}
+
+- (PSMTabDragView *)dragView
+{
+ return _dragView;
+}
+
+@end
diff --git a/Frameworks/PSMTabBar/PSMTabDragWindowController.h b/Frameworks/PSMTabBar/PSMTabDragWindowController.h
new file mode 100644
index 00000000..1727d8b8
--- /dev/null
+++ b/Frameworks/PSMTabBar/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/Frameworks/PSMTabBar/PSMTabDragWindowController.m b/Frameworks/PSMTabBar/PSMTabDragWindowController.m
new file mode 100644
index 00000000..018bca17
--- /dev/null
+++ b/Frameworks/PSMTabBar/PSMTabDragWindowController.m
@@ -0,0 +1,119 @@
+//
+// 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] retain];
+ _tearOffStyle = tearOffStyle;
+
+ if (tearOffStyle == PSMTabBarTearOffMiniwindow) {
+ [window setBackgroundColor:[NSColor clearColor]];
+ [window setHasShadow:YES];
+ }
+
+ [window setAlphaValue:kPSMTabDragWindowAlpha];
+ }
+ return self;
+}
+
+- (void)dealloc
+{
+ if (_timer) {
+ [_timer invalidate];
+ }
+
+ if (_animation) {
+ [_animation release];
+ }
+
+ [_view release];
+ [super dealloc];
+}
+
+- (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];
+ [_animation release];
+ }
+
+ //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 release], _animation = nil;
+ [timer invalidate];
+ _timer = nil;
+ }
+}
+
+@end
diff --git a/Frameworks/PSMTabBar/PSMTabStyle.h b/Frameworks/PSMTabBar/PSMTabStyle.h
new file mode 100644
index 00000000..23c826fa
--- /dev/null
+++ b/Frameworks/PSMTabBar/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/Frameworks/PSMTabBar/Styles/PSMAdiumTabStyle.h b/Frameworks/PSMTabBar/Styles/PSMAdiumTabStyle.h
new file mode 100644
index 00000000..fac9a427
--- /dev/null
+++ b/Frameworks/PSMTabBar/Styles/PSMAdiumTabStyle.h
@@ -0,0 +1,39 @@
+//
+// PSMAdiumTabStyle.h
+// PSMTabBarControl
+//
+// Created by Kent Sutherland on 5/26/06.
+// Copyright 2006 Kent Sutherland. All rights reserved.
+//
+
+#import <Cocoa/Cocoa.h>
+#import "PSMTabStyle.h"
+
+@interface PSMAdiumTabStyle : NSObject <PSMTabStyle>
+{
+ NSImage *_closeButton, *_closeButtonDown, *_closeButtonOver;
+ NSImage *_closeDirtyButton, *_closeDirtyButtonDown, *_closeDirtyButtonOver;
+ NSImage *_addTabButtonImage, *_addTabButtonPressedImage, *_addTabButtonRolloverImage;
+ NSImage *_gradientImage;
+
+ NSDictionary *_objectCountStringAttributes;
+
+ PSMTabBarOrientation orientation;
+ PSMTabBarControl *tabBar;
+
+ BOOL _drawsUnified, _drawsRight;
+}
+
+- (void)loadImages;
+
+- (BOOL)drawsUnified;
+- (void)setDrawsUnified:(BOOL)value;
+- (BOOL)drawsRight;
+- (void)setDrawsRight:(BOOL)value;
+
+- (void)drawInteriorWithTabCell:(PSMTabBarCell *)cell inView:(NSView*)controlView;
+
+- (void)encodeWithCoder:(NSCoder *)aCoder;
+- (id)initWithCoder:(NSCoder *)aDecoder;
+
+@end
diff --git a/Frameworks/PSMTabBar/Styles/PSMAdiumTabStyle.m b/Frameworks/PSMTabBar/Styles/PSMAdiumTabStyle.m
new file mode 100644
index 00000000..be5cf82d
--- /dev/null
+++ b/Frameworks/PSMTabBar/Styles/PSMAdiumTabStyle.m
@@ -0,0 +1,1057 @@
+//
+// PSMAdiumTabStyle.m
+// PSMTabBarControl
+//
+// Created by Kent Sutherland on 5/26/06.
+// Copyright 2006 Kent Sutherland. All rights reserved.
+//
+
+#import "PSMAdiumTabStyle.h"
+#import "PSMTabBarCell.h"
+#import "PSMTabBarControl.h"
+#import "NSBezierPath_AMShading.h"
+
+#define Adium_CellPadding 2
+#define Adium_MARGIN_X 4
+#define kPSMAdiumCounterPadding 3.0
+#define kPSMAdiumObjectCounterRadius 7.0
+#define kPSMAdiumCounterMinWidth 20
+
+#define kPSMTabBarControlSourceListHeight 28
+
+#define kPSMTabBarLargeImageHeight kPSMTabBarControlSourceListHeight - 4
+#define kPSMTabBarLargeImageWidth kPSMTabBarLargeImageHeight
+
+@implementation PSMAdiumTabStyle
+
+- (NSString *)name
+{
+ return @"Adium";
+}
+
+#pragma mark -
+#pragma mark Creation/Destruction
+
+- (id)init
+{
+ if ( (self = [super init]) ) {
+ [self loadImages];
+ _drawsUnified = NO;
+ _drawsRight = NO;
+
+ _objectCountStringAttributes = [[NSDictionary alloc] initWithObjectsAndKeys:[[NSFontManager sharedFontManager] convertFont:[NSFont fontWithName:@"Helvetica" size:11.0] toHaveTrait:NSBoldFontMask], NSFontAttributeName,
+ [[NSColor whiteColor] colorWithAlphaComponent:0.85], NSForegroundColorAttributeName,
+ nil, nil];
+ }
+ return self;
+}
+
+- (void)loadImages
+{
+ _closeButton = [[NSImage alloc] initByReferencingFile:[[PSMTabBarControl bundle] pathForImageResource:@"AquaTabClose_Front"]];
+ _closeButtonDown = [[NSImage alloc] initByReferencingFile:[[PSMTabBarControl bundle] pathForImageResource:@"AquaTabClose_Front_Pressed"]];
+ _closeButtonOver = [[NSImage alloc] initByReferencingFile:[[PSMTabBarControl bundle] pathForImageResource:@"AquaTabClose_Front_Rollover"]];
+
+ _closeDirtyButton = [[NSImage alloc] initByReferencingFile:[[PSMTabBarControl bundle] pathForImageResource:@"AquaTabCloseDirty_Front"]];
+ _closeDirtyButtonDown = [[NSImage alloc] initByReferencingFile:[[PSMTabBarControl bundle] pathForImageResource:@"AquaTabCloseDirty_Front_Pressed"]];
+ _closeDirtyButtonOver = [[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"]];
+
+ _gradientImage = [[NSImage alloc] initByReferencingFile:[[PSMTabBarControl bundle] pathForImageResource:@"AdiumGradient"]];
+}
+
+- (void)dealloc
+{
+ [_closeButton release];
+ [_closeButtonDown release];
+ [_closeButtonOver release];
+
+ [_closeDirtyButton release];
+ [_closeDirtyButtonDown release];
+ [_closeDirtyButtonOver release];
+
+ [_addTabButtonImage release];
+ [_addTabButtonPressedImage release];
+ [_addTabButtonRolloverImage release];
+
+ [_gradientImage release];
+
+ [_objectCountStringAttributes release];
+
+ [super dealloc];
+}
+
+#pragma mark -
+#pragma mark Drawing Style Accessors
+
+- (BOOL)drawsUnified
+{
+ return _drawsUnified;
+}
+
+- (void)setDrawsUnified:(BOOL)value
+{
+ _drawsUnified = value;
+}
+
+- (BOOL)drawsRight
+{
+ return _drawsRight;
+}
+
+- (void)setDrawsRight:(BOOL)value
+{
+ _drawsRight = value;
+}
+
+#pragma mark -
+#pragma mark Control Specific
+
+- (CGFloat)leftMarginForTabBarControl
+{
+ return 3.0f;
+}
+
+- (CGFloat)rightMarginForTabBarControl
+{
+ return 24.0f;
+}
+
+- (CGFloat)topMarginForTabBarControl
+{
+ return 10.0f;
+}
+
+- (void)setOrientation:(PSMTabBarOrientation)value
+{
+ orientation = 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)tabOrientation
+{
+ NSRect dragRect = [cell frame];
+
+ if ([cell tabState] & PSMTab_SelectedMask) {
+ if (tabOrientation == PSMTabBarHorizontalOrientation) {
+ dragRect.size.width++;
+ dragRect.size.height -= 2.0;
+ }
+ }
+
+ return dragRect;
+}
+
+- (BOOL)closeButtonIsEnabledForCell:(PSMTabBarCell *)cell
+{
+ return ([cell hasCloseButton] && ![cell isCloseButtonSuppressed]);
+
+}
+- (NSRect)closeButtonRectForTabCell:(PSMTabBarCell *)cell withFrame:(NSRect)cellFrame
+{
+ if ([self closeButtonIsEnabledForCell:cell] == NO) {
+ return NSZeroRect;
+ }
+
+ NSRect result;
+ result.size = [_closeButton size];
+
+ switch (orientation) {
+ case PSMTabBarHorizontalOrientation:
+ {
+ result.origin.x = cellFrame.origin.x + Adium_MARGIN_X;
+ result.origin.y = cellFrame.origin.y + MARGIN_Y + 2.0;
+ if ([cell state] == NSOnState) {
+ result.origin.y -= 1;
+ }
+ break;
+ }
+
+ case PSMTabBarVerticalOrientation:
+ {
+ result.origin.x = NSMaxX(cellFrame) - (Adium_MARGIN_X*2) - NSWidth(result);
+ result.origin.y = NSMinY(cellFrame) + (NSHeight(cellFrame) / 2) - (result.size.height / 2) + 1;
+ break;
+ }
+ }
+
+ return result;
+}
+
+- (NSRect)iconRectForTabCell:(PSMTabBarCell *)cell
+{
+ if ([cell hasIcon] == NO) {
+ return NSZeroRect;
+ }
+
+ NSRect cellFrame = [cell frame];
+ NSImage *icon = [[[cell representedObject] identifier] icon];
+ NSSize iconSize = [icon size];
+
+ NSRect result;
+ result.size = iconSize;
+
+ switch (orientation)
+ {
+ case PSMTabBarHorizontalOrientation:
+ result.origin.x = cellFrame.origin.x + Adium_MARGIN_X;
+ result.origin.y = cellFrame.origin.y + MARGIN_Y;
+ break;
+
+ case PSMTabBarVerticalOrientation:
+ result.origin.x = NSMaxX(cellFrame) - (Adium_MARGIN_X * 2) - NSWidth(result);
+ result.origin.y = NSMinY(cellFrame) + (NSHeight(cellFrame) / 2) - (NSHeight(result) / 2) + 1;
+ break;
+ }
+
+ // For horizontal tabs, center in available space (in case icon image is smaller than kPSMTabBarIconWidth)
+ if (orientation == PSMTabBarHorizontalOrientation) {
+ if (iconSize.width < kPSMTabBarIconWidth)
+ result.origin.x += (kPSMTabBarIconWidth - iconSize.width) / 2.0;
+ if (iconSize.height < kPSMTabBarIconWidth)
+ result.origin.y += (kPSMTabBarIconWidth - iconSize.height) / 2.0;
+ }
+
+ if ([cell state] == NSOnState) {
+ result.origin.y -= 1;
+ }
+
+ 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 - Adium_MARGIN_X - kPSMTabBarIndicatorWidth;
+ result.origin.y = cellFrame.origin.y + MARGIN_Y;
+
+ if ([cell state] == NSOnState) {
+ result.origin.y -= 1;
+ }
+
+ return result;
+}
+
+- (NSSize)sizeForObjectCounterRectForTabCell:(PSMTabBarCell *)cell
+{
+ NSSize size;
+ CGFloat countWidth = [[self attributedObjectCountValueForTabCell:cell] size].width;
+
+ countWidth += (2 * kPSMAdiumObjectCounterRadius - 6.0 + kPSMAdiumCounterPadding);
+
+ if (countWidth < kPSMAdiumCounterMinWidth) {
+ countWidth = kPSMAdiumCounterMinWidth;
+ }
+
+ size = NSMakeSize(countWidth, 2 * kPSMAdiumObjectCounterRadius); // temp
+
+ return size;
+}
+
+- (NSRect)objectCounterRectForTabCell:(PSMTabBarCell *)cell
+{
+ NSRect cellFrame;
+ NSRect result;
+
+ if ([cell count] == 0) {
+ return NSZeroRect;
+ }
+
+ cellFrame = [cell frame];
+ result.size = [self sizeForObjectCounterRectForTabCell:cell];
+ result.origin.x = NSMaxX(cellFrame) - Adium_MARGIN_X - result.size.width;
+ result.origin.y = cellFrame.origin.y + MARGIN_Y + 1.0;
+
+ if (![[cell indicator] isHidden]) {
+ result.origin.x -= kPSMTabBarIndicatorWidth + Adium_CellPadding;
+ }
+
+ return result;
+}
+
+- (CGFloat)minimumWidthOfTabCell:(PSMTabBarCell *)cell
+{
+ CGFloat resultWidth = 0.0;
+
+ // left margin
+ resultWidth = Adium_MARGIN_X;
+
+ // close button?
+ if ([self closeButtonIsEnabledForCell:cell]) {
+ resultWidth += MAX([_closeButton size].width, NSWidth([self iconRectForTabCell:cell])) + Adium_CellPadding;
+ }
+
+ // icon?
+ /*if ([cell hasIcon]) {
+ resultWidth += kPSMTabBarIconWidth + Adium_CellPadding;
+ }*/
+
+ // the label
+ resultWidth += kPSMMinimumTitleWidth;
+
+ // object counter?
+ if (([cell count] > 0) && (orientation == PSMTabBarHorizontalOrientation)) {
+ resultWidth += NSWidth([self objectCounterRectForTabCell:cell]) + Adium_CellPadding;
+ }
+
+ // indicator?
+ if ([[cell indicator] isHidden] == NO) {
+ resultWidth += Adium_CellPadding + kPSMTabBarIndicatorWidth;
+ }
+
+ // right margin
+ resultWidth += Adium_MARGIN_X;
+
+ return ceil(resultWidth);
+}
+
+- (CGFloat)desiredWidthOfTabCell:(PSMTabBarCell *)cell
+{
+ CGFloat resultWidth = 0.0;
+
+ // left margin
+ resultWidth = Adium_MARGIN_X;
+
+ // close button?
+ if ([self closeButtonIsEnabledForCell:cell]) {
+ resultWidth += MAX([_closeButton size].width, NSWidth([self iconRectForTabCell:cell])) + Adium_CellPadding;
+ }
+
+ // icon?
+ /*if ([cell hasIcon]) {
+ resultWidth += kPSMTabBarIconWidth + Adium_CellPadding;
+ }*/
+
+ // the label
+ resultWidth += [[cell attributedStringValue] size].width + Adium_CellPadding;
+
+ // object counter?
+ if (([cell count] > 0) && (orientation == PSMTabBarHorizontalOrientation)){
+ resultWidth += [self objectCounterRectForTabCell:cell].size.width + Adium_CellPadding;
+ }
+
+ // indicator?
+ if ([[cell indicator] isHidden] == NO) {
+ resultWidth += Adium_CellPadding + kPSMTabBarIndicatorWidth;
+ }
+
+ // right margin
+ resultWidth += Adium_MARGIN_X;
+
+ return ceil(resultWidth);
+}
+
+- (CGFloat)tabCellHeight
+{
+ return ((orientation == PSMTabBarHorizontalOrientation) ? kPSMTabBarControlHeight : kPSMTabBarControlSourceListHeight);
+}
+
+#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] autorelease];
+}
+
+- (NSAttributedString *)attributedStringValueForTabCell:(PSMTabBarCell *)cell
+{
+ NSMutableAttributedString *attrStr;
+ NSString *contents = [cell stringValue];
+ attrStr = [[[NSMutableAttributedString alloc] initWithString:contents] autorelease];
+ NSRange range = NSMakeRange(0, [contents length]);
+
+ // Add font attribute
+ [attrStr addAttribute:NSFontAttributeName value:[NSFont systemFontOfSize:11.0] range:range];
+ [attrStr addAttribute:NSForegroundColorAttributeName value:[NSColor controlTextColor] range:range];
+
+ // Paragraph Style for Truncating Long Text
+ static NSMutableParagraphStyle *TruncatingTailParagraphStyle = nil;
+ if (!TruncatingTailParagraphStyle) {
+ TruncatingTailParagraphStyle = [[[NSParagraphStyle defaultParagraphStyle] mutableCopy] retain];
+ [TruncatingTailParagraphStyle setLineBreakMode:NSLineBreakByTruncatingTail];
+ }
+ [attrStr addAttribute:NSParagraphStyleAttributeName value:TruncatingTailParagraphStyle range:range];
+
+ return attrStr;
+}
+
+#pragma mark -
+#pragma mark Cell Drawing
+
+- (CGFloat)heightOfAttributedString:(NSAttributedString *)inAttributedString withWidth:(CGFloat)width
+{
+ static NSMutableDictionary *cache;
+ if (!cache)
+ cache = [[NSMutableDictionary alloc] init];
+ if ([cache count] > 100) //100 items should be trivial in terms of memory overhead, but sufficient
+ [cache removeAllObjects];
+ NSNumber *cachedHeight = [cache objectForKey:inAttributedString];
+ if (cachedHeight)
+ return [cachedHeight doubleValue];
+ else {
+ NSTextStorage *textStorage = [[NSTextStorage alloc] initWithAttributedString:inAttributedString];
+ NSTextContainer *textContainer = [[NSTextContainer alloc] initWithContainerSize:NSMakeSize(width, 1e7)];
+ NSLayoutManager *layoutManager = [[NSLayoutManager alloc] init];
+
+ //Configure
+ [textContainer setLineFragmentPadding:0.0];
+ [layoutManager addTextContainer:textContainer];
+ [textStorage addLayoutManager:layoutManager];
+
+ //Force the layout manager to layout its text
+ (void)[layoutManager glyphRangeForTextContainer:textContainer];
+
+ CGFloat height = [layoutManager usedRectForTextContainer:textContainer].size.height;
+
+ [textStorage release];
+ [textContainer release];
+ [layoutManager release];
+
+ [cache setObject:[NSNumber numberWithDouble:height] forKey:inAttributedString];
+
+ return height;
+ }
+}
+
+- (void)drawObjectCounterInCell:(PSMTabBarCell *)cell withRect:(NSRect)myRect
+{
+ myRect.size.width -= kPSMAdiumCounterPadding;
+ myRect.origin.x += kPSMAdiumCounterPadding;
+
+ [[cell countColor] ?: [NSColor colorWithCalibratedWhite:0.3 alpha:0.6] set];
+ NSBezierPath *path = [NSBezierPath bezierPath];
+ [path setLineWidth:1.0];
+
+ if ([cell state] == NSOnState) {
+ myRect.origin.y -= 1.0;
+ }
+
+ [path moveToPoint:NSMakePoint(NSMinX(myRect) + kPSMAdiumObjectCounterRadius, NSMinY(myRect))];
+ [path lineToPoint:NSMakePoint(NSMaxX(myRect) - kPSMAdiumObjectCounterRadius, NSMinY(myRect))];
+ [path appendBezierPathWithArcWithCenter:NSMakePoint(NSMaxX(myRect) - kPSMAdiumObjectCounterRadius, NSMinY(myRect) + kPSMAdiumObjectCounterRadius)
+ radius:kPSMAdiumObjectCounterRadius
+ startAngle:270.0
+ endAngle:90.0];
+ [path lineToPoint:NSMakePoint(NSMinX(myRect) + kPSMAdiumObjectCounterRadius, NSMaxY(myRect))];
+ [path appendBezierPathWithArcWithCenter:NSMakePoint(NSMinX(myRect) + kPSMAdiumObjectCounterRadius, NSMinY(myRect) + kPSMAdiumObjectCounterRadius)
+ radius:kPSMAdiumObjectCounterRadius
+ 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];
+}
+
+- (NSBezierPath *)bezierPathWithRoundedRect:(NSRect)rect radius:(CGFloat)radius
+{
+ NSBezierPath *path = [NSBezierPath bezierPath];
+ NSPoint topLeft, topRight, bottomLeft, bottomRight;
+
+ topLeft = NSMakePoint(rect.origin.x, rect.origin.y);
+ topRight = NSMakePoint(rect.origin.x + rect.size.width, rect.origin.y);
+ bottomLeft = NSMakePoint(rect.origin.x, rect.origin.y + rect.size.height);
+ bottomRight = NSMakePoint(rect.origin.x + rect.size.width, rect.origin.y + rect.size.height);
+
+ [path appendBezierPathWithArcWithCenter:NSMakePoint(topLeft.x + radius, topLeft.y + radius)
+ radius:radius
+ startAngle:180
+ endAngle:270
+ clockwise:NO];
+ [path lineToPoint:NSMakePoint(topRight.x - radius, topRight.y)];
+
+ [path appendBezierPathWithArcWithCenter:NSMakePoint(topRight.x - radius, topRight.y + radius)
+ radius:radius
+ startAngle:270
+ endAngle:0
+ clockwise:NO];
+ [path lineToPoint:NSMakePoint(bottomRight.x, bottomRight.y - radius)];
+
+ [path appendBezierPathWithArcWithCenter:NSMakePoint(bottomRight.x - radius, bottomRight.y - radius)
+ radius:radius
+ startAngle:0
+ endAngle:90
+ clockwise:NO];
+ [path lineToPoint:NSMakePoint(bottomLeft.x + radius, bottomLeft.y)];
+
+ [path appendBezierPathWithArcWithCenter:NSMakePoint(bottomLeft.x + radius, bottomLeft.y - radius)
+ radius:radius
+ startAngle:90
+ endAngle:180
+ clockwise:NO];
+ [path lineToPoint:NSMakePoint(topLeft.x, topLeft.y + radius)];
+
+ return path;
+}
+
+- (void)drawInteriorWithTabCell:(PSMTabBarCell *)cell inView:(NSView*)controlView
+{
+ NSRect cellFrame = [cell frame];
+
+ if ((orientation == PSMTabBarVerticalOrientation) &&
+ [cell hasLargeImage]) {
+ NSImage *image = [[[cell representedObject] identifier] largeImage];
+ cellFrame.origin.x += Adium_MARGIN_X;
+
+ NSRect imageDrawingRect = NSMakeRect(cellFrame.origin.x,
+ cellFrame.origin.y - ((kPSMTabBarControlSourceListHeight - kPSMTabBarLargeImageHeight) / 2),
+ kPSMTabBarLargeImageWidth, kPSMTabBarLargeImageHeight);
+
+ [NSGraphicsContext saveGraphicsState];
+ //Use a transform to draw an arbitrary image in our flipped view
+ NSAffineTransform *transform = [NSAffineTransform transform];
+ [transform translateXBy:imageDrawingRect.origin.x yBy:(imageDrawingRect.origin.y + imageDrawingRect.size.height)];
+ [transform scaleXBy:1.0 yBy:-1.0];
+ [transform concat];
+
+ imageDrawingRect.origin = NSMakePoint(0,0);
+
+ //Create Rounding.
+ CGFloat userIconRoundingRadius = (kPSMTabBarLargeImageWidth / 4.0);
+ if (userIconRoundingRadius > 3) userIconRoundingRadius = 3;
+ NSBezierPath *clipPath = [self bezierPathWithRoundedRect:imageDrawingRect radius:userIconRoundingRadius];
+ [clipPath addClip];
+
+ [image drawInRect:imageDrawingRect
+ fromRect:NSMakeRect(0, 0, [image size].width, [image size].height)
+ operation:NSCompositeSourceOver
+ fraction:1.0];
+
+ [NSGraphicsContext restoreGraphicsState];
+
+ cellFrame.origin.x += imageDrawingRect.size.width;
+ cellFrame.size.width -= imageDrawingRect.size.width;
+ }
+
+ // label rect
+ NSRect labelRect;
+ labelRect.origin.x = cellFrame.origin.x + Adium_MARGIN_X;
+ labelRect.size.width = cellFrame.size.width - (labelRect.origin.x - cellFrame.origin.x) - Adium_CellPadding;
+ labelRect.size.height = cellFrame.size.height;
+ switch (orientation)
+ {
+ case PSMTabBarHorizontalOrientation:
+ labelRect.origin.y = cellFrame.origin.y + MARGIN_Y + 1.0;
+ break;
+ case PSMTabBarVerticalOrientation:
+ labelRect.origin.y = cellFrame.origin.y;
+ break;
+ }
+
+ if ([self closeButtonIsEnabledForCell:cell]) {
+ /* The close button and the icon (if present) are drawn combined, changing on-hover */
+ NSRect closeButtonRect = [cell closeButtonRectForFrame:cellFrame];
+ NSRect iconRect = [self iconRectForTabCell:cell];
+ NSRect drawingRect;
+ NSImage *closeButtonOrIcon = nil;
+
+ if ([cell hasIcon]) {
+ /* If the cell has an icon and a close button, determine which rect should be used and use it consistently
+ * This only matters for horizontal tabs; vertical tabs look fine without making this adjustment.
+ */
+ if (NSWidth(iconRect) > NSWidth(closeButtonRect)) {
+ closeButtonRect.origin.x = NSMinX(iconRect) + NSWidth(iconRect)/2 - NSWidth(closeButtonRect)/2;
+ }
+ }
+
+ if ([cell closeButtonPressed]) {
+ closeButtonOrIcon = ([cell isEdited] ? _closeDirtyButtonDown : _closeButtonDown);
+ drawingRect = closeButtonRect;
+
+ } else if ([cell closeButtonOver]) {
+ closeButtonOrIcon = ([cell isEdited] ? _closeDirtyButtonOver : _closeButtonOver);
+ drawingRect = closeButtonRect;
+
+ } else if ((orientation == PSMTabBarVerticalOrientation) &&
+ ([cell count] > 0)) {
+ /* In vertical tabs, the count indicator supercedes the icon */
+ NSSize counterSize = [self sizeForObjectCounterRectForTabCell:cell];
+ if (counterSize.width > NSWidth(closeButtonRect)) {
+ closeButtonRect.origin.x -= (counterSize.width - NSWidth(closeButtonRect));
+ closeButtonRect.size.width = counterSize.width;
+ }
+
+ closeButtonRect.origin.y = cellFrame.origin.y + ((NSHeight(cellFrame) - counterSize.height) / 2);
+ closeButtonRect.size.height = counterSize.height;
+
+ drawingRect = closeButtonRect;
+ [self drawObjectCounterInCell:cell withRect:drawingRect];
+ /* closeButtonOrIcon == nil */
+
+ } else if ([cell hasIcon]) {
+ closeButtonOrIcon = [[[cell representedObject] identifier] icon];
+ drawingRect = iconRect;
+
+ } else {
+ closeButtonOrIcon = ([cell isEdited] ? _closeDirtyButton : _closeButton);
+ drawingRect = closeButtonRect;
+ }
+
+ if ([controlView isFlipped]) {
+ drawingRect.origin.y += drawingRect.size.height;
+ }
+
+ [closeButtonOrIcon compositeToPoint:drawingRect.origin operation:NSCompositeSourceOver fraction:1.0];
+
+ // scoot label over
+ switch (orientation)
+ {
+ case PSMTabBarHorizontalOrientation:
+ {
+ CGFloat oldOrigin = labelRect.origin.x;
+ if (NSWidth(iconRect) > NSWidth(closeButtonRect)) {
+ labelRect.origin.x = (NSMaxX(iconRect) + (Adium_CellPadding * 2));
+ } else {
+ labelRect.origin.x = (NSMaxX(closeButtonRect) + (Adium_CellPadding * 2));
+ }
+ labelRect.size.width -= (NSMinX(labelRect) - oldOrigin);
+ break;
+ }
+ case PSMTabBarVerticalOrientation:
+ {
+ //Generate the remaining label rect directly from the location of the close button, allowing for padding
+ if (NSWidth(iconRect) > NSWidth(closeButtonRect)) {
+ labelRect.size.width = NSMinX(iconRect) - Adium_CellPadding - NSMinX(labelRect);
+ } else {
+ labelRect.size.width = NSMinX(closeButtonRect) - Adium_CellPadding - NSMinX(labelRect);
+ }
+
+ break;
+ }
+ }
+
+ } else if ([cell hasIcon]) {
+ /* The close button is disabled; the cell has an icon */
+ NSRect iconRect = [self iconRectForTabCell:cell];
+ NSImage *icon = [[[cell representedObject] identifier] icon];
+
+ if ([controlView isFlipped]) {
+ iconRect.origin.y += iconRect.size.height;
+ }
+
+ [icon compositeToPoint:iconRect.origin operation:NSCompositeSourceOver fraction:1.0];
+
+ // scoot label over by the size of the standard close button
+ switch (orientation)
+ {
+ case PSMTabBarHorizontalOrientation:
+ labelRect.origin.x += (NSWidth(iconRect) + Adium_CellPadding);
+ labelRect.size.width -= (NSWidth(iconRect) + Adium_CellPadding);
+ break;
+ case PSMTabBarVerticalOrientation:
+ labelRect.size.width -= (NSWidth(iconRect) + Adium_CellPadding);
+ break;
+ }
+ }
+
+ if ([cell state] == NSOnState) {
+ labelRect.origin.y -= 1;
+ }
+
+ if (![[cell indicator] isHidden]) {
+ labelRect.size.width -= (kPSMTabBarIndicatorWidth + Adium_CellPadding);
+ }
+
+ // object counter
+ //The object counter takes up space horizontally...
+ if (([cell count] > 0) &&
+ (orientation == PSMTabBarHorizontalOrientation)) {
+ NSRect counterRect = [self objectCounterRectForTabCell:cell];
+
+ [self drawObjectCounterInCell:cell withRect:counterRect];
+ labelRect.size.width -= NSWidth(counterRect) + Adium_CellPadding;
+ }
+
+ // draw label
+ NSAttributedString *attributedString = [cell attributedStringValue];
+ if (orientation == PSMTabBarVerticalOrientation) {
+ //Calculate the centered rect
+ CGFloat stringHeight = [self heightOfAttributedString:attributedString withWidth:NSWidth(labelRect)];
+ if (stringHeight < labelRect.size.height) {
+ labelRect.origin.y += (NSHeight(labelRect) - stringHeight) / 2.0;
+ }
+ }
+
+ [attributedString drawInRect:labelRect];
+}
+
+- (void)drawTabCell:(PSMTabBarCell *)cell
+{
+ NSRect cellFrame = [cell frame];
+ NSColor *lineColor = nil;
+ NSBezierPath *bezier = [NSBezierPath bezierPath];
+ lineColor = [NSColor grayColor];
+
+ [bezier setLineWidth:1.0];
+
+ //disable antialiasing of bezier paths
+ [NSGraphicsContext saveGraphicsState];
+ [[NSGraphicsContext currentContext] setShouldAntialias:NO];
+
+ NSShadow *shadow = [[NSShadow alloc] init];
+ [shadow setShadowOffset:NSMakeSize(-2, -2)];
+ [shadow setShadowBlurRadius:2];
+ [shadow setShadowColor:[NSColor colorWithCalibratedWhite:0.6 alpha:1.0]];
+
+ if ([cell state] == NSOnState) {
+ // selected tab
+ if (orientation == PSMTabBarHorizontalOrientation) {
+ NSRect aRect = NSMakeRect(cellFrame.origin.x, cellFrame.origin.y, NSWidth(cellFrame), cellFrame.size.height - 2.5);
+
+ // background
+ if (_drawsUnified) {
+ if ([[[tabBar tabView] window] isKeyWindow]) {
+ NSBezierPath *path = [NSBezierPath bezierPathWithRect:aRect];
+ [path linearGradientFillWithStartColor:[NSColor colorWithCalibratedWhite:0.835 alpha:1.0]
+ endColor:[NSColor colorWithCalibratedWhite:0.843 alpha:1.0]];
+ } else {
+ [[NSColor windowBackgroundColor] set];
+ NSRectFill(aRect);
+ }
+ } else {
+ [_gradientImage drawInRect:NSMakeRect(NSMinX(aRect), NSMinY(aRect), NSWidth(aRect), NSHeight(aRect)) fromRect:NSMakeRect(0, 0, [_gradientImage size].width, [_gradientImage size].height) operation:NSCompositeSourceOver fraction:1.0];
+ }
+
+ // frame
+ [lineColor set];
+ [bezier setLineWidth:1.0];
+ [bezier moveToPoint:NSMakePoint(aRect.origin.x, aRect.origin.y)];
+ [bezier lineToPoint:NSMakePoint(aRect.origin.x, aRect.origin.y + aRect.size.height)];
+
+ [shadow setShadowOffset:NSMakeSize(-2, -2)];
+ [shadow set];
+ [bezier stroke];
+
+ bezier = [NSBezierPath bezierPath];
+ [bezier setLineWidth:1.0];
+ [bezier moveToPoint:NSMakePoint(NSMinX(aRect), NSMaxY(aRect))];
+ [bezier lineToPoint:NSMakePoint(NSMaxX(aRect), NSMaxY(aRect))];
+ [bezier lineToPoint:NSMakePoint(NSMaxX(aRect), NSMinY(aRect))];
+
+ if ([[cell controlView] frame].size.height < 2) {
+ // special case of hidden control; need line across top of cell
+ [bezier moveToPoint:NSMakePoint(aRect.origin.x, aRect.origin.y + 0.5)];
+ [bezier lineToPoint:NSMakePoint(aRect.origin.x+aRect.size.width, aRect.origin.y + 0.5)];
+ }
+
+ [shadow setShadowOffset:NSMakeSize(2, -2)];
+ [shadow set];
+ [bezier stroke];
+ } else {
+ NSRect aRect;
+
+ if (_drawsRight) {
+ aRect = NSMakeRect(cellFrame.origin.x - 1, cellFrame.origin.y, cellFrame.size.width - 3, cellFrame.size.height);
+ } else {
+ aRect = NSMakeRect(cellFrame.origin.x + 2, cellFrame.origin.y, cellFrame.size.width - 2, cellFrame.size.height);
+ }
+
+ // background
+ if (_drawsUnified) {
+ if ([[[tabBar tabView] window] isKeyWindow]) {
+ NSBezierPath *path = [NSBezierPath bezierPathWithRect:aRect];
+ [path linearGradientFillWithStartColor:[NSColor colorWithCalibratedWhite:0.835 alpha:1.0]
+ endColor:[NSColor colorWithCalibratedWhite:0.843 alpha:1.0]];
+ } else {
+ [[NSColor windowBackgroundColor] set];
+ NSRectFill(aRect);
+ }
+ } else {
+ NSBezierPath *path = [NSBezierPath bezierPathWithRect:aRect];
+ if (_drawsRight) {
+ [path linearVerticalGradientFillWithStartColor:[NSColor colorWithCalibratedWhite:0.92 alpha:1.0]
+ endColor:[NSColor colorWithCalibratedWhite:0.98 alpha:1.0]];
+ } else {
+ [path linearVerticalGradientFillWithStartColor:[NSColor colorWithCalibratedWhite:0.98 alpha:1.0]
+ endColor:[NSColor colorWithCalibratedWhite:0.92 alpha:1.0]];
+ }
+ }
+
+ // frame
+ //top line
+ [lineColor set];
+ [bezier setLineWidth:1.0];
+ [bezier moveToPoint:NSMakePoint(NSMinX(aRect), NSMinY(aRect))];
+ [bezier lineToPoint:NSMakePoint(NSMaxX(aRect), NSMinY(aRect))];
+ [bezier stroke];
+
+ //outer edge and bottom lines
+ bezier = [NSBezierPath bezierPath];
+ [bezier setLineWidth:1.0];
+ if (_drawsRight) {
+ //Right
+ [bezier moveToPoint:NSMakePoint(NSMaxX(aRect), NSMinY(aRect))];
+ [bezier lineToPoint:NSMakePoint(NSMaxX(aRect), NSMaxY(aRect))];
+ //Bottom
+ [bezier lineToPoint:NSMakePoint(NSMinX(aRect), NSMaxY(aRect))];
+ } else {
+ //Left
+ [bezier moveToPoint:NSMakePoint(NSMinX(aRect), NSMinY(aRect))];
+ [bezier lineToPoint:NSMakePoint(NSMinX(aRect), NSMaxY(aRect))];
+ //Bottom
+ [bezier lineToPoint:NSMakePoint(NSMaxX(aRect), NSMaxY(aRect))];
+ }
+ [shadow setShadowOffset:NSMakeSize((_drawsRight ? 2 : -2), -2)];
+ [shadow set];
+ [bezier stroke];
+ }
+ } else {
+ // unselected tab
+ NSRect aRect = NSMakeRect(cellFrame.origin.x, cellFrame.origin.y, cellFrame.size.width, cellFrame.size.height);
+
+ // rollover
+ if ([cell isHighlighted]) {
+ [[NSColor colorWithCalibratedWhite:0.0 alpha:0.1] set];
+ NSRectFillUsingOperation(aRect, NSCompositeSourceAtop);
+ }
+
+ // frame
+ [lineColor set];
+
+ if (orientation == PSMTabBarHorizontalOrientation) {
+ [bezier moveToPoint:NSMakePoint(aRect.origin.x, aRect.origin.y)];
+ [bezier lineToPoint:NSMakePoint(aRect.origin.x + aRect.size.width, aRect.origin.y)];
+ if (!([cell tabState] & PSMTab_RightIsSelectedMask)) {
+ //draw the tab divider
+ [bezier lineToPoint:NSMakePoint(aRect.origin.x + aRect.size.width, aRect.origin.y + aRect.size.height)];
+ }
+ [bezier stroke];
+
+ } else {
+ //No outline for vertical
+ }
+ }
+
+ [NSGraphicsContext restoreGraphicsState];
+ [shadow release];
+
+ [self drawInteriorWithTabCell:cell inView:[cell controlView]];
+}
+
+- (void)drawBackgroundInRect:(NSRect)rect
+{
+ //Draw for our whole bounds; it'll be automatically clipped to fit the appropriate drawing area
+ rect = [tabBar bounds];
+
+ switch (orientation) {
+ case PSMTabBarHorizontalOrientation:
+ if (_drawsUnified && [[[tabBar tabView] window] isKeyWindow]) {
+ if ([[[tabBar tabView] window] isKeyWindow]) {
+ NSBezierPath *backgroundPath = [NSBezierPath bezierPathWithRect:rect];
+ [backgroundPath linearGradientFillWithStartColor:[NSColor colorWithCalibratedWhite:0.835 alpha:1.0]
+ endColor:[NSColor colorWithCalibratedWhite:0.843 alpha:1.0]];
+ } else {
+ [[NSColor windowBackgroundColor] set];
+ NSRectFill(rect);
+ }
+ } else {
+ [[NSColor colorWithCalibratedWhite:0.85 alpha:0.6] set];
+ [NSBezierPath fillRect:rect];
+ }
+ break;
+
+ case PSMTabBarVerticalOrientation:
+ //This is the Mail.app source list background color... which differs from the iTunes one.
+ [[NSColor colorWithCalibratedRed:.9059
+ green:.9294
+ blue:.9647
+ alpha:1.0] set];
+ NSRectFill(rect);
+ break;
+ }
+
+ //Draw the border and shadow around the tab bar itself
+ [NSGraphicsContext saveGraphicsState];
+ [[NSGraphicsContext currentContext] setShouldAntialias:NO];
+
+ NSShadow *shadow = [[NSShadow alloc] init];
+ [shadow setShadowBlurRadius:2];
+ [shadow setShadowColor:[NSColor colorWithCalibratedWhite:0.6 alpha:1.0]];
+
+ [[NSColor grayColor] set];
+
+ NSBezierPath *path = [NSBezierPath bezierPath];
+ [path setLineWidth:1.0];
+
+ switch (orientation) {
+ case PSMTabBarHorizontalOrientation:
+ {
+ rect.origin.y++;
+ [path moveToPoint:NSMakePoint(rect.origin.x, rect.origin.y)];
+ [path lineToPoint:NSMakePoint(rect.origin.x + rect.size.width, rect.origin.y)];
+ [shadow setShadowOffset:NSMakeSize(2, -2)];
+
+ [shadow set];
+ [path stroke];
+
+ break;
+ }
+
+ case PSMTabBarVerticalOrientation:
+ {
+ NSPoint startPoint, endPoint;
+ NSSize shadowOffset;
+
+ //Draw vertical shadow
+ if (_drawsRight) {
+ startPoint = NSMakePoint(NSMinX(rect), NSMinY(rect));
+ endPoint = NSMakePoint(NSMinX(rect), NSMaxY(rect));
+ shadowOffset = NSMakeSize(2, -2);
+ } else {
+ startPoint = NSMakePoint(NSMaxX(rect) - 1, NSMinY(rect));
+ endPoint = NSMakePoint(NSMaxX(rect) - 1, NSMaxY(rect));
+ shadowOffset = NSMakeSize(-2, -2);
+ }
+
+ [path moveToPoint:startPoint];
+ [path lineToPoint:endPoint];
+ [shadow setShadowOffset:shadowOffset];
+
+ [shadow set];
+ [path stroke];
+
+ [path removeAllPoints];
+
+ //Draw top horizontal shadow
+ startPoint = NSMakePoint(NSMinX(rect), NSMinY(rect));
+ endPoint = NSMakePoint(NSMaxX(rect), NSMinY(rect));
+ shadowOffset = NSMakeSize(0, -1);
+
+ [path moveToPoint:startPoint];
+ [path lineToPoint:endPoint];
+ [shadow setShadowOffset:shadowOffset];
+
+ [shadow set];
+ [path stroke];
+
+ break;
+ }
+ }
+
+ [shadow release];
+ [NSGraphicsContext restoreGraphicsState];
+}
+
+- (void)drawTabBar:(PSMTabBarControl *)bar inRect:(NSRect)rect
+{
+ if (orientation != [bar orientation]) {
+ orientation = [bar orientation];
+ }
+
+ if (tabBar != bar) {
+ [tabBar release];
+ tabBar = [bar retain];
+ }
+
+ [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] autorelease];
+ 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] retain];
+ [centeredParagraphStyle setAlignment:NSCenterTextAlignment];
+ }
+
+ [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
+{
+ if ([aCoder allowsKeyedCoding]) {
+ [aCoder encodeObject:_closeButton forKey:@"closeButton"];
+ [aCoder encodeObject:_closeButtonDown forKey:@"closeButtonDown"];
+ [aCoder encodeObject:_closeButtonOver forKey:@"closeButtonOver"];
+ [aCoder encodeObject:_closeDirtyButton forKey:@"closeDirtyButton"];
+ [aCoder encodeObject:_closeDirtyButtonDown forKey:@"closeDirtyButtonDown"];
+ [aCoder encodeObject:_closeDirtyButtonOver forKey:@"closeDirtyButtonOver"];
+ [aCoder encodeObject:_addTabButtonImage forKey:@"addTabButtonImage"];
+ [aCoder encodeObject:_addTabButtonPressedImage forKey:@"addTabButtonPressedImage"];
+ [aCoder encodeObject:_addTabButtonRolloverImage forKey:@"addTabButtonRolloverImage"];
+ [aCoder encodeBool:_drawsUnified forKey:@"drawsUnified"];
+ [aCoder encodeBool:_drawsRight forKey:@"drawsRight"];
+ }
+}
+
+- (id)initWithCoder:(NSCoder *)aDecoder
+{
+ if ( (self = [super init]) ) {
+ if ([aDecoder allowsKeyedCoding]) {
+ _closeButton = [[aDecoder decodeObjectForKey:@"closeButton"] retain];
+ _closeButtonDown = [[aDecoder decodeObjectForKey:@"closeButtonDown"] retain];
+ _closeButtonOver = [[aDecoder decodeObjectForKey:@"closeButtonOver"] retain];
+ _closeDirtyButton = [[aDecoder decodeObjectForKey:@"closeDirtyButton"] retain];
+ _closeDirtyButtonDown = [[aDecoder decodeObjectForKey:@"closeDirtyButtonDown"] retain];
+ _closeDirtyButtonOver = [[aDecoder decodeObjectForKey:@"closeDirtyButtonOver"] retain];
+ _addTabButtonImage = [[aDecoder decodeObjectForKey:@"addTabButtonImage"] retain];
+ _addTabButtonPressedImage = [[aDecoder decodeObjectForKey:@"addTabButtonPressedImage"] retain];
+ _addTabButtonRolloverImage = [[aDecoder decodeObjectForKey:@"addTabButtonRolloverImage"] retain];
+ _drawsUnified = [aDecoder decodeBoolForKey:@"drawsUnified"];
+ _drawsRight = [aDecoder decodeBoolForKey:@"drawsRight"];
+ }
+ }
+ return self;
+}
+
+@end
diff --git a/Frameworks/PSMTabBar/Styles/PSMAquaTabStyle.h b/Frameworks/PSMTabBar/Styles/PSMAquaTabStyle.h
new file mode 100644
index 00000000..d3448e41
--- /dev/null
+++ b/Frameworks/PSMTabBar/Styles/PSMAquaTabStyle.h
@@ -0,0 +1,38 @@
+//
+// PSMAquaTabStyle.h
+// PSMTabBarControl
+//
+// Created by John Pannell on 2/17/06.
+// Copyright 2006 Positive Spin Media. All rights reserved.
+//
+
+#import <Cocoa/Cocoa.h>
+#import "PSMTabStyle.h"
+
+@interface PSMAquaTabStyle : NSObject <PSMTabStyle> {
+ NSImage *aquaTabBg;
+ NSImage *aquaTabBgDown;
+ NSImage *aquaTabBgDownGraphite;
+ NSImage *aquaTabBgDownNonKey;
+ NSImage *aquaDividerDown;
+ NSImage *aquaDivider;
+ NSImage *aquaCloseButton;
+ NSImage *aquaCloseButtonDown;
+ NSImage *aquaCloseButtonOver;
+ NSImage *aquaCloseDirtyButton;
+ NSImage *aquaCloseDirtyButtonDown;
+ NSImage *aquaCloseDirtyButtonOver;
+ NSImage *_addTabButtonImage;
+ NSImage *_addTabButtonPressedImage;
+ NSImage *_addTabButtonRolloverImage;
+
+ NSDictionary *_objectCountStringAttributes;
+ PSMTabBarControl *tabBar;
+}
+
+- (void)loadImages;
+- (void)drawInteriorWithTabCell:(PSMTabBarCell *)cell inView:(NSView*)controlView;
+
+- (void)encodeWithCoder:(NSCoder *)aCoder;
+- (id)initWithCoder:(NSCoder *)aDecoder;
+@end
diff --git a/Frameworks/PSMTabBar/Styles/PSMAquaTabStyle.m b/Frameworks/PSMTabBar/Styles/PSMAquaTabStyle.m
new file mode 100644
index 00000000..f2618fe5
--- /dev/null
+++ b/Frameworks/PSMTabBar/Styles/PSMAquaTabStyle.m
@@ -0,0 +1,579 @@
+//
+// PSMAquaTabStyle.m
+// PSMTabBarControl
+//
+// Created by John Pannell on 2/17/06.
+// Copyright 2006 Positive Spin Media. All rights reserved.
+//
+
+#import "PSMAquaTabStyle.h"
+#import "PSMTabBarCell.h"
+#import "PSMTabBarControl.h"
+
+#define kPSMAquaObjectCounterRadius 7.0
+#define kPSMAquaCounterMinWidth 20
+
+@implementation PSMAquaTabStyle
+
+- (NSString *)name
+{
+ return @"Aqua";
+}
+
+#pragma mark -
+#pragma mark Creation/Destruction
+
+- (id) init
+{
+ if ( (self = [super init]) ) {
+ [self loadImages];
+
+ _objectCountStringAttributes = [[NSDictionary alloc] initWithObjectsAndKeys:[[NSFontManager sharedFontManager] convertFont:[NSFont fontWithName:@"Helvetica" size:11.0] toHaveTrait:NSBoldFontMask], NSFontAttributeName,
+ [[NSColor whiteColor] colorWithAlphaComponent:0.85], NSForegroundColorAttributeName,
+ nil, nil];
+ }
+ return self;
+}
+
+- (void) loadImages
+{
+ // Aqua Tabs Images
+ aquaTabBg = [[NSImage alloc] initByReferencingFile:[[PSMTabBarControl bundle] pathForImageResource:@"AquaTabsBackground"]];
+ [aquaTabBg setFlipped:YES];
+
+ aquaTabBgDown = [[NSImage alloc] initByReferencingFile:[[PSMTabBarControl bundle] pathForImageResource:@"AquaTabsDown"]];
+ [aquaTabBgDown setFlipped:YES];
+
+ aquaTabBgDownGraphite = [[NSImage alloc] initByReferencingFile:[[PSMTabBarControl bundle] pathForImageResource:@"AquaTabsDownGraphite"]];
+ [aquaTabBgDown setFlipped:YES];
+
+ aquaTabBgDownNonKey = [[NSImage alloc] initByReferencingFile:[[PSMTabBarControl bundle] pathForImageResource:@"AquaTabsDownNonKey"]];
+ [aquaTabBgDown setFlipped:YES];
+
+ aquaDividerDown = [[NSImage alloc] initByReferencingFile:[[PSMTabBarControl bundle] pathForImageResource:@"AquaTabsSeparatorDown"]];
+ [aquaDivider setFlipped:NO];
+
+ aquaDivider = [[NSImage alloc] initByReferencingFile:[[PSMTabBarControl bundle] pathForImageResource:@"AquaTabsSeparator"]];
+ [aquaDivider setFlipped:NO];
+
+ aquaCloseButton = [[NSImage alloc] initByReferencingFile:[[PSMTabBarControl bundle] pathForImageResource:@"AquaTabClose_Front"]];
+ aquaCloseButtonDown = [[NSImage alloc] initByReferencingFile:[[PSMTabBarControl bundle] pathForImageResource:@"AquaTabClose_Front_Pressed"]];
+ aquaCloseButtonOver = [[NSImage alloc] initByReferencingFile:[[PSMTabBarControl bundle] pathForImageResource:@"AquaTabClose_Front_Rollover"]];
+
+ aquaCloseDirtyButton = [[NSImage alloc] initByReferencingFile:[[PSMTabBarControl bundle] pathForImageResource:@"AquaTabCloseDirty_Front"]];
+ aquaCloseDirtyButtonDown = [[NSImage alloc] initByReferencingFile:[[PSMTabBarControl bundle] pathForImageResource:@"AquaTabCloseDirty_Front_Pressed"]];
+ aquaCloseDirtyButtonOver = [[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"]];
+}
+
+- (void)dealloc
+{
+ [aquaTabBg release];
+ [aquaTabBgDown release];
+ [aquaDividerDown release];
+ [aquaDivider release];
+ [aquaCloseButton release];
+ [aquaCloseButtonDown release];
+ [aquaCloseButtonOver release];
+ [aquaCloseDirtyButton release];
+ [aquaCloseDirtyButtonDown release];
+ [aquaCloseDirtyButtonOver release];
+ [_addTabButtonImage release];
+ [_addTabButtonPressedImage release];
+ [_addTabButtonRolloverImage release];
+
+ [_objectCountStringAttributes release];
+
+ [super dealloc];
+}
+
+#pragma mark -
+#pragma mark Control Specifics
+
+- (CGFloat)leftMarginForTabBarControl
+{
+ return 0.0f;
+}
+
+- (CGFloat)rightMarginForTabBarControl
+{
+ return 24.0f;
+}
+
+- (CGFloat)topMarginForTabBarControl
+{
+ return 0.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 Specifics
+
+- (NSRect)dragRectForTabCell:(PSMTabBarCell *)cell orientation:(PSMTabBarOrientation)orientation
+{
+ return [cell frame];
+}
+
+- (NSRect)closeButtonRectForTabCell:(PSMTabBarCell *)cell withFrame:(NSRect)cellFrame
+{
+ if ([cell hasCloseButton] == NO) {
+ return NSZeroRect;
+ }
+
+ NSRect result;
+ result.size = [aquaCloseButton size];
+ result.origin.x = cellFrame.origin.x + MARGIN_X;
+ result.origin.y = cellFrame.origin.y + MARGIN_Y + 2.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;
+
+ if ([cell hasCloseButton] && ![cell isCloseButtonSuppressed]) {
+ result.origin.x += [aquaCloseButton 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;
+
+ 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 * kPSMAquaObjectCounterRadius - 6.0);
+ if (countWidth < kPSMAquaCounterMinWidth) {
+ countWidth = kPSMAquaCounterMinWidth;
+ }
+
+ NSRect result;
+ result.size = NSMakeSize(countWidth, 2 * kPSMAquaObjectCounterRadius); // 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 += [aquaCloseButton 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 += [aquaCloseButton 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] autorelease];
+}
+
+- (NSAttributedString *)attributedStringValueForTabCell:(PSMTabBarCell *)cell
+{
+ NSMutableAttributedString *attrStr;
+ NSString * contents = [cell stringValue];
+ attrStr = [[[NSMutableAttributedString alloc] initWithString:contents] autorelease];
+ 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] retain];
+ [TruncatingTailParagraphStyle setLineBreakMode:NSLineBreakByTruncatingTail];
+ [TruncatingTailParagraphStyle setAlignment:NSCenterTextAlignment];
+ }
+ [attrStr addAttribute:NSParagraphStyleAttributeName value:TruncatingTailParagraphStyle range:range];
+
+ return attrStr;
+}
+
+#pragma mark -
+#pragma mark Drawing
+
+- (void)drawTabCell:(PSMTabBarCell *)cell;
+{
+ NSRect cellFrame = [cell frame];
+
+ // Selected Tab
+ if ([cell state] == NSOnState) {
+ NSRect aRect = NSMakeRect(cellFrame.origin.x, cellFrame.origin.y, cellFrame.size.width, cellFrame.size.height-2.5);
+ aRect.size.height -= 0.5;
+
+ // proper tint
+ NSControlTint currentTint;
+ if ([cell controlTint] == NSDefaultControlTint)
+ currentTint = [NSColor currentControlTint];
+ else
+ currentTint = [cell controlTint];
+
+ if (![[[cell controlView] window] isKeyWindow])
+ currentTint = NSClearControlTint;
+
+ NSImage *bgImage;
+ switch (currentTint) {
+ case NSGraphiteControlTint:
+ bgImage = aquaTabBgDownGraphite;
+ break;
+ case NSClearControlTint:
+ bgImage = aquaTabBgDownNonKey;
+ break;
+ case NSBlueControlTint:
+ default:
+ bgImage = aquaTabBgDown;
+ break;
+ }
+
+ [bgImage drawInRect:cellFrame fromRect:NSMakeRect(0.0, 0.0, 1.0, 22.0) operation:NSCompositeSourceOver fraction:1.0];
+ [aquaDivider compositeToPoint:NSMakePoint(cellFrame.origin.x + cellFrame.size.width - 1.0, cellFrame.origin.y + cellFrame.size.height) operation:NSCompositeSourceOver];
+
+ aRect.size.height+=0.5;
+
+ } 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, NSCompositeSourceAtop);
+ }
+
+ [aquaDivider compositeToPoint:NSMakePoint(cellFrame.origin.x + cellFrame.size.width - 1.0, cellFrame.origin.y + cellFrame.size.height) operation:NSCompositeSourceOver];
+ }
+
+ [self drawInteriorWithTabCell:cell inView:[cell controlView]];
+}
+
+- (void)drawBackgroundInRect:(NSRect)rect
+{
+ if (rect.size.height <= 22.0) {
+ //Draw for our whole bounds; it'll be automatically clipped to fit the appropriate drawing area
+ rect = [tabBar bounds];
+
+ [aquaTabBg drawInRect:rect fromRect:NSMakeRect(0.0, 0.0, 1.0, 22.0) operation:NSCompositeSourceOver fraction:1.0];
+ }
+}
+
+- (void)drawTabBar:(PSMTabBarControl *)bar inRect:(NSRect)rect
+{
+ if (tabBar != bar) {
+ 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] autorelease];
+ 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] retain];
+ [centeredParagraphStyle setAlignment:NSCenterTextAlignment];
+ }
+ [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];
+ }
+ }
+}
+
+- (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] ? aquaCloseDirtyButton : aquaCloseButton;
+
+ if ([cell closeButtonOver]) closeButton = [cell isEdited] ? aquaCloseDirtyButtonOver : aquaCloseButtonOver;
+ if ([cell closeButtonPressed]) closeButton = [cell isEdited] ? aquaCloseDirtyButtonDown : aquaCloseButtonDown;
+
+ closeButtonSize = [closeButton size];
+
+ if ([controlView isFlipped]) {
+ closeButtonRect.origin.y += closeButtonRect.size.height;
+ }
+
+ [closeButton compositeToPoint:closeButtonRect.origin operation:NSCompositeSourceOver 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 compositeToPoint:iconRect.origin operation:NSCompositeSourceOver 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;
+ labelRect.size.height = cellFrame.size.height;
+ labelRect.origin.y = cellFrame.origin.y + MARGIN_Y + 1.0;
+
+ if (![[cell indicator] isHidden]) {
+ labelRect.size.width -= (kPSMTabBarIndicatorWidth + kPSMTabBarCellPadding);
+ }
+
+ // object counter
+ if ([cell count] > 0) {
+ [[cell countColor] ?: [NSColor colorWithCalibratedWhite:0.3 alpha:0.45] set];
+ NSBezierPath *path = [NSBezierPath bezierPath];
+ NSRect myRect = [self objectCounterRectForTabCell:cell];
+ [path moveToPoint:NSMakePoint(myRect.origin.x + kPSMAquaObjectCounterRadius, myRect.origin.y)];
+ [path lineToPoint:NSMakePoint(myRect.origin.x + myRect.size.width - kPSMAquaObjectCounterRadius, myRect.origin.y)];
+ [path appendBezierPathWithArcWithCenter:NSMakePoint(myRect.origin.x + myRect.size.width - kPSMAquaObjectCounterRadius, myRect.origin.y + kPSMAquaObjectCounterRadius) radius:kPSMAquaObjectCounterRadius startAngle:270.0 endAngle:90.0];
+ [path lineToPoint:NSMakePoint(myRect.origin.x + kPSMAquaObjectCounterRadius, myRect.origin.y + myRect.size.height)];
+ [path appendBezierPathWithArcWithCenter:NSMakePoint(myRect.origin.x + kPSMAquaObjectCounterRadius, myRect.origin.y + kPSMAquaObjectCounterRadius) radius:kPSMAquaObjectCounterRadius 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;
+ }
+
+ // Draw Label
+ [[cell attributedStringValue] drawInRect:labelRect];
+}
+
+#pragma mark -
+#pragma mark Archiving
+
+- (void)encodeWithCoder:(NSCoder *)aCoder {
+
+ //[super encodeWithCoder:aCoder];
+/*
+ if ([aCoder allowsKeyedCoding]) {
+ [aCoder encodeObject:aquaTabBg forKey:@"aquaTabBg"];
+ [aCoder encodeObject:aquaTabBgDown forKey:@"aquaTabBgDown"];
+ [aCoder encodeObject:aquaTabBgDownGraphite forKey:@"aquaTabBgDownGraphite"];
+ [aCoder encodeObject:aquaTabBgDownNonKey forKey:@"aquaTabBgDownNonKey"];
+ [aCoder encodeObject:aquaDividerDown forKey:@"aquaDividerDown"];
+ [aCoder encodeObject:aquaDivider forKey:@"aquaDivider"];
+ [aCoder encodeObject:aquaCloseButton forKey:@"aquaCloseButton"];
+ [aCoder encodeObject:aquaCloseButtonDown forKey:@"aquaCloseButtonDown"];
+ [aCoder encodeObject:aquaCloseButtonOver forKey:@"aquaCloseButtonOver"];
+ [aCoder encodeObject:aquaCloseDirtyButton forKey:@"aquaCloseDirtyButton"];
+ [aCoder encodeObject:aquaCloseDirtyButtonDown forKey:@"aquaCloseDirtyButtonDown"];
+ [aCoder encodeObject:aquaCloseDirtyButtonOver forKey:@"aquaCloseDirtyButtonOver"];
+ [aCoder encodeObject:_addTabButtonImage forKey:@"addTabButtonImage"];
+ [aCoder encodeObject:_addTabButtonPressedImage forKey:@"addTabButtonPressedImage"];
+ [aCoder encodeObject:_addTabButtonRolloverImage forKey:@"addTabButtonRolloverImage"];
+ }
+ */
+}
+
+- (id)initWithCoder:(NSCoder *)aDecoder {
+
+ self = [self init];
+ if (self) {
+/*
+ if ([aDecoder allowsKeyedCoding]) {
+ aquaTabBg = [[aDecoder decodeObjectForKey:@"aquaTabBg"] retain];
+ aquaTabBgDown = [[aDecoder decodeObjectForKey:@"aquaTabBgDown"] retain];
+ aquaTabBgDownGraphite = [[aDecoder decodeObjectForKey:@"aquaTabBgDownGraphite"] retain];
+ aquaTabBgDownNonKey = [[aDecoder decodeObjectForKey:@"aquaTabBgDownNonKey"] retain];
+ aquaDividerDown = [[aDecoder decodeObjectForKey:@"aquaDividerDown"] retain];
+ aquaDivider = [[aDecoder decodeObjectForKey:@"aquaDivider"] retain];
+ aquaCloseButton = [[aDecoder decodeObjectForKey:@"aquaCloseButton"] retain];
+ aquaCloseButtonDown = [[aDecoder decodeObjectForKey:@"aquaCloseButtonDown"] retain];
+ aquaCloseButtonOver = [[aDecoder decodeObjectForKey:@"aquaCloseButtonOver"] retain];
+ aquaCloseDirtyButton = [[aDecoder decodeObjectForKey:@"aquaCloseDirtyButton"] retain];
+ aquaCloseDirtyButtonDown = [[aDecoder decodeObjectForKey:@"aquaCloseDirtyButtonDown"] retain];
+ aquaCloseDirtyButtonOver = [[aDecoder decodeObjectForKey:@"aquaCloseDirtyButtonOver"] retain];
+ _addTabButtonImage = [[aDecoder decodeObjectForKey:@"addTabButtonImage"] retain];
+ _addTabButtonPressedImage = [[aDecoder decodeObjectForKey:@"addTabButtonPressedImage"] retain];
+ _addTabButtonRolloverImage = [[aDecoder decodeObjectForKey:@"addTabButtonRolloverImage"] retain];
+
+ }
+*/
+ }
+ return self;
+}
+
+@end
diff --git a/Frameworks/PSMTabBar/Styles/PSMCardTabStyle.h b/Frameworks/PSMTabBar/Styles/PSMCardTabStyle.h
new file mode 100644
index 00000000..b04f83d1
--- /dev/null
+++ b/Frameworks/PSMTabBar/Styles/PSMCardTabStyle.h
@@ -0,0 +1,32 @@
+//
+// PSMCardTabStyle.h
+// Fichiers
+//
+// Created by Michael Monscheuer on 05.11.09.
+// Copyright 2009 WriteFlow KG, Wien. All rights reserved.
+//
+
+#import <Cocoa/Cocoa.h>
+#import "PSMTabStyle.h"
+
+@interface PSMCardTabStyle : 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/Frameworks/PSMTabBar/Styles/PSMCardTabStyle.m b/Frameworks/PSMTabBar/Styles/PSMCardTabStyle.m
new file mode 100644
index 00000000..31da000c
--- /dev/null
+++ b/Frameworks/PSMTabBar/Styles/PSMCardTabStyle.m
@@ -0,0 +1,649 @@
+//
+// PSMCardTabStyle.m
+// Fichiers
+//
+// Created by Michael Monscheuer on 05.11.09.
+// Copyright 2009 WriteFlow KG, Wien. All rights reserved.
+//
+
+#import "PSMCardTabStyle.h"
+#import "PSMTabBarCell.h"
+#import "PSMTabBarControl.h"
+#import "NSBezierPath_AMShading.h"
+
+#define kPSMUnifiedObjectCounterRadius 7.0
+#define kPSMUnifiedCounterMinWidth 20
+
+@interface PSMCardTabStyle (Private)
+- (void)drawInteriorWithTabCell:(PSMTabBarCell *)cell inView:(NSView*)controlView;
+@end
+
+@implementation PSMCardTabStyle
+
+- (NSString *)name
+{
+ return @"Card";
+}
+
+#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;
+}
+
+- (void)dealloc
+{
+ [unifiedCloseButton release];
+ [unifiedCloseButtonDown release];
+ [unifiedCloseButtonOver release];
+ [unifiedCloseDirtyButton release];
+ [unifiedCloseDirtyButtonDown release];
+ [unifiedCloseDirtyButtonOver release];
+ [_addTabButtonImage release];
+ [_addTabButtonPressedImage release];
+ [_addTabButtonRolloverImage release];
+
+ [_objectCountStringAttributes release];
+
+ [super dealloc];
+}
+
+#pragma mark -
+#pragma mark Control Specific
+
+- (void)setLeftMarginForTabBarControl:(CGFloat)margin
+{
+ leftMargin = margin;
+}
+
+- (CGFloat)leftMarginForTabBarControl
+{
+ return leftMargin;
+}
+
+- (CGFloat)rightMarginForTabBarControl
+{
+ return 5.0f;
+// 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 || [cell isCloseButtonSuppressed]) {
+ 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] autorelease];
+}
+
+- (NSAttributedString *)attributedStringValueForTabCell:(PSMTabBarCell *)cell
+{
+ NSMutableAttributedString *attrStr;
+ NSString * contents = [cell stringValue];
+ attrStr = [[[NSMutableAttributedString alloc] initWithString:contents] autorelease];
+ 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] retain];
+ [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];
+ BOOL showsBaselineSeparator = NO;
+/*
+ NSToolbar *toolbar = [[[cell controlView] window] toolbar];
+ BOOL showsBaselineSeparator = (toolbar && [toolbar respondsToSelector:@selector(showsBaselineSeparator)] && [toolbar showsBaselineSeparator]);
+*/
+ if (!showsBaselineSeparator) {
+ cellFrame.origin.y += 3.0;
+ cellFrame.size.height -= 3.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);
+ // selected tab
+ NSRect aRect = NSMakeRect(cellFrame.origin.x+.5, cellFrame.origin.y+0.5, cellFrame.size.width-1.0, cellFrame.size.height-1.0);
+
+ // frame
+ CGFloat radius = MIN(6.0, 0.5f * MIN(NSWidth(aRect), NSHeight(aRect)))-0.5;
+// NSRect rect = NSInsetRect(aRect, radius, radius);
+
+ [bezier moveToPoint: NSMakePoint(NSMinX(aRect),NSMaxY(aRect))];
+ [bezier appendBezierPathWithArcFromPoint:NSMakePoint(NSMinX(aRect),NSMinY(aRect)) toPoint:NSMakePoint(NSMidX(aRect),NSMinY(aRect)) radius:radius];
+ [bezier appendBezierPathWithArcFromPoint:NSMakePoint(NSMaxX(aRect),NSMinY(aRect)) toPoint:NSMakePoint(NSMaxX(aRect),NSMaxY(aRect)) radius:radius];
+ [bezier lineToPoint: NSMakePoint(NSMaxX(aRect),NSMaxY(aRect))];
+
+/*
+ [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, NSCompositeSourceAtop);
+ }
+
+ // 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 redColor] 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 redColor] 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];
+
+ BOOL showsBaselineSeparator = NO;
+/*
+ NSToolbar *toolbar = [[[cell controlView] window] toolbar];
+ BOOL showsBaselineSeparator = (toolbar && [toolbar respondsToSelector:@selector(showsBaselineSeparator)] && [toolbar showsBaselineSeparator]);
+*/
+ if (!showsBaselineSeparator) {
+ cellFrame.origin.y += 3.0;
+ cellFrame.size.height -= 3.0;
+ }
+
+ 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 compositeToPoint:closeButtonRect.origin operation:NSCompositeSourceOver 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 compositeToPoint:iconRect.origin operation:NSCompositeSourceOver 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 + 1;
+ 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];
+
+
+ if (![[[tabBar tabView] window] isKeyWindow]) {
+ [[NSColor windowBackgroundColor] set];
+ NSRectFill(gradientRect);
+ }
+}
+
+- (void)drawTabBar:(PSMTabBarControl *)bar inRect:(NSRect)rect
+{
+ [NSGraphicsContext saveGraphicsState];
+
+ // draw button separator
+ for(PSMTabBarCell *cell in [bar cells])
+ {
+ if([cell state] == NSOnState)
+ {
+ [[NSColor colorWithCalibratedWhite:0.576 alpha:1.0] set];
+
+ [NSBezierPath strokeLineFromPoint:NSMakePoint(rect.origin.x,NSMaxY(rect)-0.5)
+ toPoint:NSMakePoint(NSMinX([cell frame]),NSMaxY(rect)-0.5)];
+ [NSBezierPath strokeLineFromPoint:NSMakePoint(NSMaxX([cell frame]),NSMaxY(rect)-0.5)
+ toPoint:NSMakePoint(NSMaxX(rect),NSMaxY(rect)-0.5)];
+ }
+ }
+
+ 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] autorelease];
+ 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:NSCenterTextAlignment];
+ }
+ [attrStr addAttribute:NSParagraphStyleAttributeName value:centeredParagraphStyle range:range];
+ [centeredParagraphStyle release];
+ [attrStr drawInRect:labelRect];
+
+ goto EXIT;
+ }
+
+ // 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];
+ }
+ }
+
+EXIT:
+ [NSGraphicsContext restoreGraphicsState];
+
+}
+
+#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 = [self init];
+ if (self) {
+ /*
+ if ([aDecoder allowsKeyedCoding]) {
+ unifiedCloseButton = [[aDecoder decodeObjectForKey:@"unifiedCloseButton"] retain];
+ unifiedCloseButtonDown = [[aDecoder decodeObjectForKey:@"unifiedCloseButtonDown"] retain];
+ unifiedCloseButtonOver = [[aDecoder decodeObjectForKey:@"unifiedCloseButtonOver"] retain];
+ unifiedCloseDirtyButton = [[aDecoder decodeObjectForKey:@"unifiedCloseDirtyButton"] retain];
+ unifiedCloseDirtyButtonDown = [[aDecoder decodeObjectForKey:@"unifiedCloseDirtyButtonDown"] retain];
+ unifiedCloseDirtyButtonOver = [[aDecoder decodeObjectForKey:@"unifiedCloseDirtyButtonOver"] retain];
+ _addTabButtonImage = [[aDecoder decodeObjectForKey:@"addTabButtonImage"] retain];
+ _addTabButtonPressedImage = [[aDecoder decodeObjectForKey:@"addTabButtonPressedImage"] retain];
+ _addTabButtonRolloverImage = [[aDecoder decodeObjectForKey:@"addTabButtonRolloverImage"] retain];
+ }
+ */
+ }
+ return self;
+}
+
+@end
diff --git a/Frameworks/PSMTabBar/Styles/PSMMetalTabStyle.h b/Frameworks/PSMTabBar/Styles/PSMMetalTabStyle.h
new file mode 100644
index 00000000..22cb7bc8
--- /dev/null
+++ b/Frameworks/PSMTabBar/Styles/PSMMetalTabStyle.h
@@ -0,0 +1,34 @@
+//
+// PSMMetalTabStyle.h
+// PSMTabBarControl
+//
+// Created by John Pannell on 2/17/06.
+// Copyright 2006 Positive Spin Media. All rights reserved.
+//
+
+#import <Cocoa/Cocoa.h>
+#import "PSMTabStyle.h"
+
+@interface PSMMetalTabStyle : NSObject <PSMTabStyle> {
+ NSImage *metalCloseButton;
+ NSImage *metalCloseButtonDown;
+ NSImage *metalCloseButtonOver;
+ NSImage *metalCloseDirtyButton;
+ NSImage *metalCloseDirtyButtonDown;
+ NSImage *metalCloseDirtyButtonOver;
+ NSImage *_addTabButtonImage;
+ NSImage *_addTabButtonPressedImage;
+ NSImage *_addTabButtonRolloverImage;
+
+ NSDictionary *_objectCountStringAttributes;
+
+ PSMTabBarOrientation orientation;
+ PSMTabBarControl *tabBar;
+}
+
+- (void)drawInteriorWithTabCell:(PSMTabBarCell *)cell inView:(NSView*)controlView;
+
+- (void)encodeWithCoder:(NSCoder *)aCoder;
+- (id)initWithCoder:(NSCoder *)aDecoder;
+
+@end
diff --git a/Frameworks/PSMTabBar/Styles/PSMMetalTabStyle.m b/Frameworks/PSMTabBar/Styles/PSMMetalTabStyle.m
new file mode 100644
index 00000000..443179ce
--- /dev/null
+++ b/Frameworks/PSMTabBar/Styles/PSMMetalTabStyle.m
@@ -0,0 +1,656 @@
+//
+// PSMMetalTabStyle.m
+// PSMTabBarControl
+//
+// Created by John Pannell on 2/17/06.
+// Copyright 2006 Positive Spin Media. All rights reserved.
+//
+
+#import "PSMMetalTabStyle.h"
+#import "PSMTabBarCell.h"
+#import "PSMTabBarControl.h"
+
+#define kPSMMetalObjectCounterRadius 7.0
+#define kPSMMetalCounterMinWidth 20
+
+@implementation PSMMetalTabStyle
+
+- (NSString *)name
+{
+ return @"Metal";
+}
+
+#pragma mark -
+#pragma mark Creation/Destruction
+
+- (id) init
+{
+ if ( (self = [super init]) ) {
+ metalCloseButton = [[NSImage alloc] initByReferencingFile:[[PSMTabBarControl bundle] pathForImageResource:@"TabClose_Front"]];
+ metalCloseButtonDown = [[NSImage alloc] initByReferencingFile:[[PSMTabBarControl bundle] pathForImageResource:@"TabClose_Front_Pressed"]];
+ metalCloseButtonOver = [[NSImage alloc] initByReferencingFile:[[PSMTabBarControl bundle] pathForImageResource:@"TabClose_Front_Rollover"]];
+
+ metalCloseDirtyButton = [[NSImage alloc] initByReferencingFile:[[PSMTabBarControl bundle] pathForImageResource:@"TabClose_Dirty"]];
+ metalCloseDirtyButtonDown = [[NSImage alloc] initByReferencingFile:[[PSMTabBarControl bundle] pathForImageResource:@"TabClose_Dirty_Pressed"]];
+ metalCloseDirtyButtonOver = [[NSImage alloc] initByReferencingFile:[[PSMTabBarControl bundle] pathForImageResource:@"TabClose_Dirty_Rollover"]];
+
+ _addTabButtonImage = [[NSImage alloc] initByReferencingFile:[[PSMTabBarControl bundle] pathForImageResource:@"TabNewMetal"]];
+ _addTabButtonPressedImage = [[NSImage alloc] initByReferencingFile:[[PSMTabBarControl bundle] pathForImageResource:@"TabNewMetalPressed"]];
+ _addTabButtonRolloverImage = [[NSImage alloc] initByReferencingFile:[[PSMTabBarControl bundle] pathForImageResource:@"TabNewMetalRollover"]];
+
+ _objectCountStringAttributes = [[NSDictionary alloc] initWithObjectsAndKeys:[[NSFontManager sharedFontManager] convertFont:[NSFont fontWithName:@"Helvetica" size:11.0] toHaveTrait:NSBoldFontMask], NSFontAttributeName,
+ [[NSColor whiteColor] colorWithAlphaComponent:0.85], NSForegroundColorAttributeName,
+ nil, nil];
+ }
+ return self;
+}
+
+- (void)dealloc
+{
+ [metalCloseButton release];
+ [metalCloseButtonDown release];
+ [metalCloseButtonOver release];
+ [metalCloseDirtyButton release];
+ [metalCloseDirtyButtonDown release];
+ [metalCloseDirtyButtonOver release];
+ [_addTabButtonImage release];
+ [_addTabButtonPressedImage release];
+ [_addTabButtonRolloverImage release];
+
+ [_objectCountStringAttributes release];
+
+ [super dealloc];
+}
+
+#pragma mark -
+#pragma mark Control Specific
+
+- (CGFloat)leftMarginForTabBarControl
+{
+ return 10.0f;
+}
+
+- (CGFloat)rightMarginForTabBarControl
+{
+ return 24.0f;
+}
+
+- (CGFloat)topMarginForTabBarControl
+{
+ return 10.0f;
+}
+
+- (void)setOrientation:(PSMTabBarOrientation)value
+{
+ orientation = 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)tabOrientation
+{
+ NSRect dragRect = [cell frame];
+ dragRect.size.width++;
+
+ if ([cell tabState] & PSMTab_SelectedMask) {
+ if (tabOrientation == PSMTabBarHorizontalOrientation) {
+ dragRect.size.height -= 2.0;
+ } else {
+ dragRect.size.height += 1.0;
+ dragRect.origin.y -= 1.0;
+ dragRect.origin.x += 2.0;
+ dragRect.size.width -= 3.0;
+ }
+ } else if (tabOrientation == PSMTabBarVerticalOrientation) {
+ dragRect.origin.x--;
+ }
+
+ return dragRect;
+}
+
+- (NSRect)closeButtonRectForTabCell:(PSMTabBarCell *)cell withFrame:(NSRect)cellFrame
+{
+ if ([cell hasCloseButton] == NO) {
+ return NSZeroRect;
+ }
+
+ NSRect result;
+ result.size = [metalCloseButton size];
+ result.origin.x = cellFrame.origin.x + MARGIN_X;
+ result.origin.y = cellFrame.origin.y + MARGIN_Y + 2.0;
+
+ if ([cell state] == NSOnState) {
+ result.origin.y -= 1;
+ }
+
+ 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;
+
+ if ([cell hasCloseButton] && ![cell isCloseButtonSuppressed]) {
+ result.origin.x += [metalCloseButton size].width + kPSMTabBarCellPadding;
+ }
+
+ if ([cell state] == NSOnState) {
+ result.origin.y -= 1;
+ }
+
+ 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;
+
+ if ([cell state] == NSOnState) {
+ result.origin.y -= 1;
+ }
+
+ 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 * kPSMMetalObjectCounterRadius - 6.0);
+ if (countWidth < kPSMMetalCounterMinWidth) {
+ countWidth = kPSMMetalCounterMinWidth;
+ }
+
+ NSRect result;
+ result.size = NSMakeSize(countWidth, 2 * kPSMMetalObjectCounterRadius); // 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 += [metalCloseButton 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 += [metalCloseButton 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] autorelease];
+}
+
+- (NSAttributedString *)attributedStringValueForTabCell:(PSMTabBarCell *)cell
+{
+ NSMutableAttributedString *attrStr;
+ NSString *contents = [cell stringValue];
+ attrStr = [[[NSMutableAttributedString alloc] initWithString:contents] autorelease];
+ NSRange range = NSMakeRange(0, [contents length]);
+
+ // Add font attribute
+ [attrStr addAttribute:NSFontAttributeName value:[NSFont boldSystemFontOfSize:11.0] range:range];
+ [attrStr addAttribute:NSForegroundColorAttributeName value:[[NSColor textColor] colorWithAlphaComponent:0.75] range:range];
+
+ // Add shadow attribute
+ NSShadow* shadow;
+ shadow = [[[NSShadow alloc] init] autorelease];
+ CGFloat shadowAlpha;
+ if (([cell state] == NSOnState) || [cell isHighlighted]) {
+ shadowAlpha = 0.8;
+ } else {
+ shadowAlpha = 0.5;
+ }
+ [shadow setShadowColor:[NSColor colorWithCalibratedWhite:1.0 alpha:shadowAlpha]];
+ [shadow setShadowOffset:NSMakeSize(0, -1)];
+ [shadow setShadowBlurRadius:1.0];
+ [attrStr addAttribute:NSShadowAttributeName value:shadow range:range];
+
+ // Paragraph Style for Truncating Long Text
+ static NSMutableParagraphStyle *TruncatingTailParagraphStyle = nil;
+ if (!TruncatingTailParagraphStyle) {
+ TruncatingTailParagraphStyle = [[[NSParagraphStyle defaultParagraphStyle] mutableCopy] retain];
+ [TruncatingTailParagraphStyle setLineBreakMode:NSLineBreakByTruncatingTail];
+ [TruncatingTailParagraphStyle setAlignment:NSCenterTextAlignment];
+ }
+ [attrStr addAttribute:NSParagraphStyleAttributeName value:TruncatingTailParagraphStyle range:range];
+
+ return attrStr;
+}
+
+#pragma mark -
+#pragma mark ---- drawing ----
+
+- (void)drawTabCell:(PSMTabBarCell *)cell
+{
+ NSRect cellFrame = [cell frame];
+ NSColor *lineColor = nil;
+ NSBezierPath *bezier = [NSBezierPath bezierPath];
+ lineColor = [NSColor darkGrayColor];
+
+ //disable antialiasing of bezier paths
+ [NSGraphicsContext saveGraphicsState];
+ [[NSGraphicsContext currentContext] setShouldAntialias:NO];
+
+ if ([cell state] == NSOnState) {
+ // selected tab
+ if (orientation == PSMTabBarHorizontalOrientation) {
+ NSRect aRect = NSMakeRect(cellFrame.origin.x, cellFrame.origin.y, cellFrame.size.width, cellFrame.size.height-2.5);
+
+ // background
+ aRect.origin.x += 1.0;
+ aRect.size.width--;
+ aRect.size.height -= 0.5;
+ NSDrawWindowBackground(aRect);
+ aRect.size.width++;
+ aRect.size.height += 0.5;
+
+ // frame
+ aRect.origin.x -= 0.5;
+ [lineColor set];
+ [bezier setLineWidth:1.0];
+ [bezier moveToPoint:NSMakePoint(aRect.origin.x, aRect.origin.y)];
+ [bezier lineToPoint:NSMakePoint(aRect.origin.x, aRect.origin.y+aRect.size.height-1.5)];
+ [bezier lineToPoint:NSMakePoint(aRect.origin.x+1.5, aRect.origin.y+aRect.size.height)];
+ [bezier lineToPoint:NSMakePoint(aRect.origin.x+aRect.size.width-2.5, aRect.origin.y+aRect.size.height)];
+ [bezier lineToPoint:NSMakePoint(aRect.origin.x+aRect.size.width, aRect.origin.y+aRect.size.height-1.5)];
+ [bezier lineToPoint:NSMakePoint(aRect.origin.x+aRect.size.width, aRect.origin.y)];
+ if ([[cell controlView] frame].size.height < 2) {
+ // special case of hidden control; need line across top of cell
+ [bezier moveToPoint:NSMakePoint(aRect.origin.x, aRect.origin.y+0.5)];
+ [bezier lineToPoint:NSMakePoint(aRect.origin.x+aRect.size.width, aRect.origin.y+0.5)];
+ }
+ } else {
+ NSRect aRect = NSMakeRect(cellFrame.origin.x + 2, cellFrame.origin.y, cellFrame.size.width - 2, cellFrame.size.height);
+
+ // background
+ aRect.origin.x++;
+ aRect.size.height--;
+ NSDrawWindowBackground(aRect);
+ aRect.origin.x--;
+ aRect.size.height++;
+
+ // frame
+ [lineColor set];
+ [bezier setLineWidth:1.0];
+ [bezier moveToPoint:NSMakePoint(aRect.origin.x + aRect.size.width, aRect.origin.y)];
+ [bezier lineToPoint:NSMakePoint(aRect.origin.x + 2, aRect.origin.y)];
+ [bezier lineToPoint:NSMakePoint(aRect.origin.x + 0.5, aRect.origin.y + 2)];
+ [bezier lineToPoint:NSMakePoint(aRect.origin.x + 0.5, aRect.origin.y + aRect.size.height - 3)];
+ [bezier lineToPoint:NSMakePoint(aRect.origin.x + 3, aRect.origin.y + aRect.size.height)];
+ [bezier lineToPoint:NSMakePoint(aRect.origin.x + aRect.size.width, aRect.origin.y + aRect.size.height)];
+ }
+
+ [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;
+
+ // rollover
+ if ([cell isHighlighted]) {
+ [[NSColor colorWithCalibratedWhite:0.0 alpha:0.1] set];
+ NSRectFillUsingOperation(aRect, NSCompositeSourceAtop);
+ }
+
+ [lineColor set];
+
+ if (orientation == PSMTabBarHorizontalOrientation) {
+ aRect.origin.x -= 1;
+ aRect.size.width += 1;
+
+ // frame
+ [bezier moveToPoint:NSMakePoint(aRect.origin.x, aRect.origin.y)];
+ [bezier lineToPoint:NSMakePoint(aRect.origin.x + aRect.size.width, aRect.origin.y)];
+ if (!([cell tabState] & PSMTab_RightIsSelectedMask)) {
+ [bezier lineToPoint:NSMakePoint(aRect.origin.x + aRect.size.width, aRect.origin.y + aRect.size.height)];
+ }
+ } else {
+ if (!([cell tabState] & PSMTab_LeftIsSelectedMask)) {
+ [bezier moveToPoint:NSMakePoint(aRect.origin.x, aRect.origin.y)];
+ [bezier lineToPoint:NSMakePoint(aRect.origin.x + aRect.size.width, aRect.origin.y)];
+ }
+
+ if (!([cell tabState] & PSMTab_RightIsSelectedMask)) {
+ [bezier moveToPoint:NSMakePoint(aRect.origin.x, aRect.origin.y + aRect.size.height)];
+ [bezier lineToPoint:NSMakePoint(aRect.origin.x + aRect.size.width, aRect.origin.y + aRect.size.height)];
+ }
+ }
+ [bezier stroke];
+ }
+
+ [NSGraphicsContext restoreGraphicsState];
+
+ [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] ? metalCloseDirtyButton : metalCloseButton;
+ if ([cell closeButtonOver]) closeButton = [cell isEdited] ? metalCloseDirtyButtonOver : metalCloseButtonOver;
+ if ([cell closeButtonPressed]) closeButton = [cell isEdited] ? metalCloseDirtyButtonDown : metalCloseButtonDown;
+
+ closeButtonSize = [closeButton size];
+ if ([controlView isFlipped]) {
+ closeButtonRect.origin.y += closeButtonRect.size.height;
+ }
+
+ [closeButton compositeToPoint:closeButtonRect.origin operation:NSCompositeSourceOver 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 compositeToPoint:iconRect.origin operation:NSCompositeSourceOver 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;
+ labelRect.size.height = cellFrame.size.height;
+ labelRect.origin.y = cellFrame.origin.y + MARGIN_Y + 1.0;
+
+ if ([cell state] == NSOnState) {
+ labelRect.origin.y -= 1;
+ }
+
+ 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];
+ if ([cell state] == NSOnState) {
+ myRect.origin.y -= 1.0;
+ }
+ [path moveToPoint:NSMakePoint(myRect.origin.x + kPSMMetalObjectCounterRadius, myRect.origin.y)];
+ [path lineToPoint:NSMakePoint(myRect.origin.x + myRect.size.width - kPSMMetalObjectCounterRadius, myRect.origin.y)];
+ [path appendBezierPathWithArcWithCenter:NSMakePoint(myRect.origin.x + myRect.size.width - kPSMMetalObjectCounterRadius, myRect.origin.y + kPSMMetalObjectCounterRadius) radius:kPSMMetalObjectCounterRadius startAngle:270.0 endAngle:90.0];
+ [path lineToPoint:NSMakePoint(myRect.origin.x + kPSMMetalObjectCounterRadius, myRect.origin.y + myRect.size.height)];
+ [path appendBezierPathWithArcWithCenter:NSMakePoint(myRect.origin.x + kPSMMetalObjectCounterRadius, myRect.origin.y + kPSMMetalObjectCounterRadius) radius:kPSMMetalObjectCounterRadius 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];
+
+ // shrink label width to make room for object counter
+ labelRect.size.width -= myRect.size.width + kPSMTabBarCellPadding;
+ }
+
+ // draw 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];
+
+ if (orientation == PSMTabBarVerticalOrientation && [tabBar frame].size.width < 2) {
+ return;
+ }
+
+ [NSGraphicsContext saveGraphicsState];
+ [[NSGraphicsContext currentContext] setShouldAntialias:NO];
+
+ [[NSColor colorWithCalibratedWhite:0.0 alpha:0.2] set];
+ NSRectFillUsingOperation(rect, NSCompositeSourceAtop);
+ [[NSColor darkGrayColor] set];
+
+ if (orientation == PSMTabBarHorizontalOrientation) {
+ [NSBezierPath strokeLineFromPoint:NSMakePoint(rect.origin.x, rect.origin.y + 0.5) toPoint:NSMakePoint(rect.origin.x + rect.size.width, rect.origin.y + 0.5)];
+ [NSBezierPath strokeLineFromPoint:NSMakePoint(rect.origin.x, rect.origin.y + rect.size.height - 0.5) toPoint:NSMakePoint(rect.origin.x + rect.size.width, rect.origin.y + rect.size.height - 0.5)];
+ } else {
+ [NSBezierPath strokeLineFromPoint:NSMakePoint(rect.origin.x, rect.origin.y + 0.5) toPoint:NSMakePoint(rect.origin.x, rect.origin.y + rect.size.height + 0.5)];
+ [NSBezierPath strokeLineFromPoint:NSMakePoint(rect.origin.x + rect.size.width, rect.origin.y + 0.5) toPoint:NSMakePoint(rect.origin.x + rect.size.width, rect.origin.y + rect.size.height + 0.5)];
+ }
+
+ [NSGraphicsContext restoreGraphicsState];
+}
+
+- (void)drawTabBar:(PSMTabBarControl *)bar inRect:(NSRect)rect
+{
+ if (orientation != [bar orientation]) {
+ orientation = [bar orientation];
+ }
+
+ if (tabBar != bar) {
+ 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] autorelease];
+ 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] retain];
+ [centeredParagraphStyle setAlignment:NSCenterTextAlignment];
+ }
+ [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:metalCloseButton forKey:@"metalCloseButton"];
+ [aCoder encodeObject:metalCloseButtonDown forKey:@"metalCloseButtonDown"];
+ [aCoder encodeObject:metalCloseButtonOver forKey:@"metalCloseButtonOver"];
+ [aCoder encodeObject:metalCloseDirtyButton forKey:@"metalCloseDirtyButton"];
+ [aCoder encodeObject:metalCloseDirtyButtonDown forKey:@"metalCloseDirtyButtonDown"];
+ [aCoder encodeObject:metalCloseDirtyButtonOver forKey:@"metalCloseDirtyButtonOver"];
+ [aCoder encodeObject:_addTabButtonImage forKey:@"addTabButtonImage"];
+ [aCoder encodeObject:_addTabButtonPressedImage forKey:@"addTabButtonPressedImage"];
+ [aCoder encodeObject:_addTabButtonRolloverImage forKey:@"addTabButtonRolloverImage"];
+ }
+*/
+}
+
+- (id)initWithCoder:(NSCoder *)aDecoder
+{
+ self = [self init];
+ if (self) {
+
+/*
+ if ([aDecoder allowsKeyedCoding]) {
+ metalCloseButton = [[aDecoder decodeObjectForKey:@"metalCloseButton"] retain];
+ metalCloseButtonDown = [[aDecoder decodeObjectForKey:@"metalCloseButtonDown"] retain];
+ metalCloseButtonOver = [[aDecoder decodeObjectForKey:@"metalCloseButtonOver"] retain];
+ metalCloseDirtyButton = [[aDecoder decodeObjectForKey:@"metalCloseDirtyButton"] retain];
+ metalCloseDirtyButtonDown = [[aDecoder decodeObjectForKey:@"metalCloseDirtyButtonDown"] retain];
+ metalCloseDirtyButtonOver = [[aDecoder decodeObjectForKey:@"metalCloseDirtyButtonOver"] retain];
+ _addTabButtonImage = [[aDecoder decodeObjectForKey:@"addTabButtonImage"] retain];
+ _addTabButtonPressedImage = [[aDecoder decodeObjectForKey:@"addTabButtonPressedImage"] retain];
+ _addTabButtonRolloverImage = [[aDecoder decodeObjectForKey:@"addTabButtonRolloverImage"] retain];
+ }
+*/
+ }
+ return self;
+}
+
+@end
diff --git a/Frameworks/PSMTabBar/Styles/PSMUnifiedTabStyle.h b/Frameworks/PSMTabBar/Styles/PSMUnifiedTabStyle.h
new file mode 100644
index 00000000..d44bbcdc
--- /dev/null
+++ b/Frameworks/PSMTabBar/Styles/PSMUnifiedTabStyle.h
@@ -0,0 +1,30 @@
+//
+// 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/Frameworks/PSMTabBar/Styles/PSMUnifiedTabStyle.m b/Frameworks/PSMTabBar/Styles/PSMUnifiedTabStyle.m
new file mode 100644
index 00000000..13fb0994
--- /dev/null
+++ b/Frameworks/PSMTabBar/Styles/PSMUnifiedTabStyle.m
@@ -0,0 +1,603 @@
+//
+// 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;
+}
+
+- (void)dealloc
+{
+ [unifiedCloseButton release];
+ [unifiedCloseButtonDown release];
+ [unifiedCloseButtonOver release];
+ [unifiedCloseDirtyButton release];
+ [unifiedCloseDirtyButtonDown release];
+ [unifiedCloseDirtyButtonOver release];
+ [_addTabButtonImage release];
+ [_addTabButtonPressedImage release];
+ [_addTabButtonRolloverImage release];
+
+ [_objectCountStringAttributes release];
+
+ [super dealloc];
+}
+
+#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] autorelease];
+}
+
+- (NSAttributedString *)attributedStringValueForTabCell:(PSMTabBarCell *)cell
+{
+ NSMutableAttributedString *attrStr;
+ NSString * contents = [cell stringValue];
+ attrStr = [[[NSMutableAttributedString alloc] initWithString:contents] autorelease];
+ 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] retain];
+ [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, NSCompositeSourceAtop);
+ }
+
+ // 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 compositeToPoint:closeButtonRect.origin operation:NSCompositeSourceOver 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 compositeToPoint:iconRect.origin operation:NSCompositeSourceOver 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] autorelease];
+ 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] retain];
+ [centeredParagraphStyle setAlignment:NSCenterTextAlignment];
+ }
+ [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 = [self init];
+ if (self) {
+ /*
+ if ([aDecoder allowsKeyedCoding]) {
+ unifiedCloseButton = [[aDecoder decodeObjectForKey:@"unifiedCloseButton"] retain];
+ unifiedCloseButtonDown = [[aDecoder decodeObjectForKey:@"unifiedCloseButtonDown"] retain];
+ unifiedCloseButtonOver = [[aDecoder decodeObjectForKey:@"unifiedCloseButtonOver"] retain];
+ unifiedCloseDirtyButton = [[aDecoder decodeObjectForKey:@"unifiedCloseDirtyButton"] retain];
+ unifiedCloseDirtyButtonDown = [[aDecoder decodeObjectForKey:@"unifiedCloseDirtyButtonDown"] retain];
+ unifiedCloseDirtyButtonOver = [[aDecoder decodeObjectForKey:@"unifiedCloseDirtyButtonOver"] retain];
+ _addTabButtonImage = [[aDecoder decodeObjectForKey:@"addTabButtonImage"] retain];
+ _addTabButtonPressedImage = [[aDecoder decodeObjectForKey:@"addTabButtonPressedImage"] retain];
+ _addTabButtonRolloverImage = [[aDecoder decodeObjectForKey:@"addTabButtonRolloverImage"] retain];
+ }
+ */
+ }
+ return self;
+}
+
+@end