diff options
author | rowanbeentje <rowan@beent.je> | 2010-06-07 23:31:59 +0000 |
---|---|---|
committer | rowanbeentje <rowan@beent.je> | 2010-06-07 23:31:59 +0000 |
commit | 664c0e238ec9feb873cfabc6e5ab5e43213323f9 (patch) | |
tree | e7dbac121287ac3d6ec170c844aa159497189188 /Frameworks/PSMTabBar | |
parent | ceffd765f621e4ad1f9d4d6775c8e55d2f136bfb (diff) | |
download | sequelpro-664c0e238ec9feb873cfabc6e5ab5e43213323f9.tar.gz sequelpro-664c0e238ec9feb873cfabc6e5ab5e43213323f9.tar.bz2 sequelpro-664c0e238ec9feb873cfabc6e5ab5e43213323f9.zip |
- Replace the precompiled PSMTabBar framework with a build-from-source dependency, in preparation for future code and style changes
Diffstat (limited to 'Frameworks/PSMTabBar')
63 files changed, 9409 insertions, 0 deletions
diff --git a/Frameworks/PSMTabBar/Images/AdiumGradient.png b/Frameworks/PSMTabBar/Images/AdiumGradient.png Binary files differnew file mode 100644 index 00000000..d410a88a --- /dev/null +++ b/Frameworks/PSMTabBar/Images/AdiumGradient.png diff --git a/Frameworks/PSMTabBar/Images/AquaTabCloseDirty_Front.png b/Frameworks/PSMTabBar/Images/AquaTabCloseDirty_Front.png Binary files differnew file mode 100644 index 00000000..77d22050 --- /dev/null +++ b/Frameworks/PSMTabBar/Images/AquaTabCloseDirty_Front.png diff --git a/Frameworks/PSMTabBar/Images/AquaTabCloseDirty_Front_Pressed.png b/Frameworks/PSMTabBar/Images/AquaTabCloseDirty_Front_Pressed.png Binary files differnew file mode 100644 index 00000000..197ea95c --- /dev/null +++ b/Frameworks/PSMTabBar/Images/AquaTabCloseDirty_Front_Pressed.png diff --git a/Frameworks/PSMTabBar/Images/AquaTabCloseDirty_Front_Rollover.png b/Frameworks/PSMTabBar/Images/AquaTabCloseDirty_Front_Rollover.png Binary files differnew file mode 100644 index 00000000..2dfe5777 --- /dev/null +++ b/Frameworks/PSMTabBar/Images/AquaTabCloseDirty_Front_Rollover.png diff --git a/Frameworks/PSMTabBar/Images/AquaTabClose_Front.png b/Frameworks/PSMTabBar/Images/AquaTabClose_Front.png Binary files differnew file mode 100644 index 00000000..02b72d39 --- /dev/null +++ b/Frameworks/PSMTabBar/Images/AquaTabClose_Front.png diff --git a/Frameworks/PSMTabBar/Images/AquaTabClose_Front_Pressed.png b/Frameworks/PSMTabBar/Images/AquaTabClose_Front_Pressed.png Binary files differnew file mode 100644 index 00000000..f81125a0 --- /dev/null +++ b/Frameworks/PSMTabBar/Images/AquaTabClose_Front_Pressed.png diff --git a/Frameworks/PSMTabBar/Images/AquaTabClose_Front_Rollover.png b/Frameworks/PSMTabBar/Images/AquaTabClose_Front_Rollover.png Binary files differnew file mode 100644 index 00000000..4f6b865f --- /dev/null +++ b/Frameworks/PSMTabBar/Images/AquaTabClose_Front_Rollover.png diff --git a/Frameworks/PSMTabBar/Images/AquaTabNew.png b/Frameworks/PSMTabBar/Images/AquaTabNew.png Binary files differnew file mode 100644 index 00000000..10a83705 --- /dev/null +++ b/Frameworks/PSMTabBar/Images/AquaTabNew.png diff --git a/Frameworks/PSMTabBar/Images/AquaTabNewPressed.png b/Frameworks/PSMTabBar/Images/AquaTabNewPressed.png Binary files differnew file mode 100644 index 00000000..cb4dd10f --- /dev/null +++ b/Frameworks/PSMTabBar/Images/AquaTabNewPressed.png diff --git a/Frameworks/PSMTabBar/Images/AquaTabNewRollover.png b/Frameworks/PSMTabBar/Images/AquaTabNewRollover.png Binary files differnew file mode 100644 index 00000000..4d469f8a --- /dev/null +++ b/Frameworks/PSMTabBar/Images/AquaTabNewRollover.png diff --git a/Frameworks/PSMTabBar/Images/AquaTabsBackground.png b/Frameworks/PSMTabBar/Images/AquaTabsBackground.png Binary files differnew file mode 100644 index 00000000..b9cd1d0f --- /dev/null +++ b/Frameworks/PSMTabBar/Images/AquaTabsBackground.png diff --git a/Frameworks/PSMTabBar/Images/AquaTabsDown.png b/Frameworks/PSMTabBar/Images/AquaTabsDown.png Binary files differnew file mode 100644 index 00000000..6fed84c6 --- /dev/null +++ b/Frameworks/PSMTabBar/Images/AquaTabsDown.png diff --git a/Frameworks/PSMTabBar/Images/AquaTabsDownGraphite.png b/Frameworks/PSMTabBar/Images/AquaTabsDownGraphite.png Binary files differnew file mode 100644 index 00000000..15bcc19c --- /dev/null +++ b/Frameworks/PSMTabBar/Images/AquaTabsDownGraphite.png diff --git a/Frameworks/PSMTabBar/Images/AquaTabsDownNonKey.png b/Frameworks/PSMTabBar/Images/AquaTabsDownNonKey.png Binary files differnew file mode 100644 index 00000000..df2c1365 --- /dev/null +++ b/Frameworks/PSMTabBar/Images/AquaTabsDownNonKey.png diff --git a/Frameworks/PSMTabBar/Images/AquaTabsSeparator.png b/Frameworks/PSMTabBar/Images/AquaTabsSeparator.png Binary files differnew file mode 100644 index 00000000..be82692d --- /dev/null +++ b/Frameworks/PSMTabBar/Images/AquaTabsSeparator.png diff --git a/Frameworks/PSMTabBar/Images/AquaTabsSeparatorDown.png b/Frameworks/PSMTabBar/Images/AquaTabsSeparatorDown.png Binary files differnew file mode 100644 index 00000000..72b7878b --- /dev/null +++ b/Frameworks/PSMTabBar/Images/AquaTabsSeparatorDown.png diff --git a/Frameworks/PSMTabBar/Images/TabClose_Dirty.png b/Frameworks/PSMTabBar/Images/TabClose_Dirty.png Binary files differnew file mode 100644 index 00000000..60a25ba3 --- /dev/null +++ b/Frameworks/PSMTabBar/Images/TabClose_Dirty.png diff --git a/Frameworks/PSMTabBar/Images/TabClose_Dirty_Pressed.png b/Frameworks/PSMTabBar/Images/TabClose_Dirty_Pressed.png Binary files differnew file mode 100644 index 00000000..978dc1c7 --- /dev/null +++ b/Frameworks/PSMTabBar/Images/TabClose_Dirty_Pressed.png diff --git a/Frameworks/PSMTabBar/Images/TabClose_Dirty_Rollover.png b/Frameworks/PSMTabBar/Images/TabClose_Dirty_Rollover.png Binary files differnew file mode 100644 index 00000000..7b8924da --- /dev/null +++ b/Frameworks/PSMTabBar/Images/TabClose_Dirty_Rollover.png diff --git a/Frameworks/PSMTabBar/Images/TabClose_Front.png b/Frameworks/PSMTabBar/Images/TabClose_Front.png Binary files differnew file mode 100644 index 00000000..e7bf88e7 --- /dev/null +++ b/Frameworks/PSMTabBar/Images/TabClose_Front.png diff --git a/Frameworks/PSMTabBar/Images/TabClose_Front_Pressed.png b/Frameworks/PSMTabBar/Images/TabClose_Front_Pressed.png Binary files differnew file mode 100644 index 00000000..feaf7281 --- /dev/null +++ b/Frameworks/PSMTabBar/Images/TabClose_Front_Pressed.png diff --git a/Frameworks/PSMTabBar/Images/TabClose_Front_Rollover.png b/Frameworks/PSMTabBar/Images/TabClose_Front_Rollover.png Binary files differnew file mode 100644 index 00000000..865bd1fb --- /dev/null +++ b/Frameworks/PSMTabBar/Images/TabClose_Front_Rollover.png diff --git a/Frameworks/PSMTabBar/Images/TabNewMetal.png b/Frameworks/PSMTabBar/Images/TabNewMetal.png Binary files differnew file mode 100644 index 00000000..be02d708 --- /dev/null +++ b/Frameworks/PSMTabBar/Images/TabNewMetal.png diff --git a/Frameworks/PSMTabBar/Images/TabNewMetalPressed.png b/Frameworks/PSMTabBar/Images/TabNewMetalPressed.png Binary files differnew file mode 100644 index 00000000..18118ec3 --- /dev/null +++ b/Frameworks/PSMTabBar/Images/TabNewMetalPressed.png diff --git a/Frameworks/PSMTabBar/Images/TabNewMetalRollover.png b/Frameworks/PSMTabBar/Images/TabNewMetalRollover.png Binary files differnew file mode 100644 index 00000000..b1308164 --- /dev/null +++ b/Frameworks/PSMTabBar/Images/TabNewMetalRollover.png diff --git a/Frameworks/PSMTabBar/Images/overflowImage.png b/Frameworks/PSMTabBar/Images/overflowImage.png Binary files differnew file mode 100644 index 00000000..2b762555 --- /dev/null +++ b/Frameworks/PSMTabBar/Images/overflowImage.png diff --git a/Frameworks/PSMTabBar/Images/overflowImagePressed.png b/Frameworks/PSMTabBar/Images/overflowImagePressed.png Binary files differnew file mode 100644 index 00000000..b3918b34 --- /dev/null +++ b/Frameworks/PSMTabBar/Images/overflowImagePressed.png diff --git a/Frameworks/PSMTabBar/Images/pi.png b/Frameworks/PSMTabBar/Images/pi.png Binary files differnew file mode 100644 index 00000000..4d598dc7 --- /dev/null +++ b/Frameworks/PSMTabBar/Images/pi.png 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 |