Permalink
Fetching contributors…
Cannot retrieve contributors at this time
952 lines (784 sloc) 28.9 KB
//
// MAAttachedWindow.m
//
// Created by Matt Gemmell on 27/09/2007.
// Copyright 2007 Magic Aubergine.
//
#import "MAAttachedWindow.h"
#define MAATTACHEDWINDOW_DEFAULT_BACKGROUND_COLOR [NSColor colorWithCalibratedWhite:0.1 alpha:0.75]
#define MAATTACHEDWINDOW_DEFAULT_BORDER_COLOR [NSColor whiteColor]
#define MAATTACHEDWINDOW_SCALE_FACTOR [[NSScreen mainScreen] userSpaceScaleFactor]
@interface MAAttachedWindow (MAPrivateMethods)
// Geometry
- (void)_updateGeometry;
- (MAWindowPosition)_bestSideForAutomaticPosition;
- (float)_arrowInset;
// Drawing
- (void)_updateBackground;
- (NSColor *)_backgroundColorPatternImage;
- (NSBezierPath *)_backgroundPath;
- (void)_appendArrowToPath:(NSBezierPath *)path;
- (void)_redisplay;
@end
@implementation MAAttachedWindow
#pragma mark Initializers
- (MAAttachedWindow *)initWithView:(NSView *)view
attachedToPoint:(NSPoint)point
inWindow:(NSWindow *)window
onSide:(MAWindowPosition)side
atDistance:(float)distance
{
// Insist on having a valid view.
if (!view) {
return nil;
}
// Create dummy initial contentRect for window.
NSRect contentRect = NSZeroRect;
contentRect.size = [view frame].size;
if ((self = [super initWithContentRect:contentRect
styleMask:NSBorderlessWindowMask
backing:NSBackingStoreBuffered
defer:NO])) {
_view = view;
_window = window;
_point = point;
_side = side;
_distance = distance;
// Configure window characteristics.
[super setBackgroundColor:[NSColor clearColor]];
[self setMovableByWindowBackground:NO];
[self setExcludedFromWindowsMenu:YES];
[self setAlphaValue:1.0];
[self setOpaque:NO];
[self setHasShadow:YES];
[self useOptimizedDrawing:YES];
// Set up some sensible defaults for display.
_MABackgroundColor = [MAATTACHEDWINDOW_DEFAULT_BACKGROUND_COLOR copy];
borderColor = [MAATTACHEDWINDOW_DEFAULT_BORDER_COLOR copy];
borderWidth = 2.0;
viewMargin = 2.0;
arrowBaseWidth = 20.0;
arrowHeight = 16.0;
hasArrow = YES;
cornerRadius = 8.0;
drawsRoundCornerBesideArrow = YES;
_resizing = NO;
// Work out what side to put the window on if it's "automatic".
if (_side == MAPositionAutomatic) {
_side = [self _bestSideForAutomaticPosition];
}
// Configure our initial geometry.
[self _updateGeometry];
// Update the background.
[self _updateBackground];
// Add view as subview of our contentView.
[[self contentView] addSubview:_view];
// Subscribe to notifications for when we change size.
[[NSNotificationCenter defaultCenter] addObserver:self
selector:@selector(windowDidResize:)
name:NSWindowDidResizeNotification
object:self];
}
return self;
}
- (MAAttachedWindow *)initWithView:(NSView *)view
attachedToPoint:(NSPoint)point
inWindow:(NSWindow *)window
atDistance:(float)distance
{
return [self initWithView:view attachedToPoint:point
inWindow:window onSide:MAPositionAutomatic
atDistance:distance];
}
- (MAAttachedWindow *)initWithView:(NSView *)view
attachedToPoint:(NSPoint)point
onSide:(MAWindowPosition)side
atDistance:(float)distance
{
return [self initWithView:view attachedToPoint:point
inWindow:nil onSide:side
atDistance:distance];
}
- (MAAttachedWindow *)initWithView:(NSView *)view
attachedToPoint:(NSPoint)point
atDistance:(float)distance
{
return [self initWithView:view attachedToPoint:point
inWindow:nil onSide:MAPositionAutomatic
atDistance:distance];
}
- (MAAttachedWindow *)initWithView:(NSView *)view
attachedToPoint:(NSPoint)point
inWindow:(NSWindow *)window
{
return [self initWithView:view attachedToPoint:point
inWindow:window onSide:MAPositionAutomatic
atDistance:0];
}
- (MAAttachedWindow *)initWithView:(NSView *)view
attachedToPoint:(NSPoint)point
onSide:(MAWindowPosition)side
{
return [self initWithView:view attachedToPoint:point
inWindow:nil onSide:side
atDistance:0];
}
- (MAAttachedWindow *)initWithView:(NSView *)view
attachedToPoint:(NSPoint)point
{
return [self initWithView:view attachedToPoint:point
inWindow:nil onSide:MAPositionAutomatic
atDistance:0];
}
- (void)dealloc
{
[[NSNotificationCenter defaultCenter] removeObserver:self];
[borderColor release];
[_MABackgroundColor release];
[super dealloc];
}
#pragma mark Geometry
- (void)_updateGeometry
{
NSRect contentRect = NSZeroRect;
contentRect.size = [_view frame].size;
// Account for viewMargin.
_viewFrame = NSMakeRect(viewMargin * MAATTACHEDWINDOW_SCALE_FACTOR,
viewMargin * MAATTACHEDWINDOW_SCALE_FACTOR,
[_view frame].size.width, [_view frame].size.height);
contentRect = NSInsetRect(contentRect,
-viewMargin * MAATTACHEDWINDOW_SCALE_FACTOR,
-viewMargin * MAATTACHEDWINDOW_SCALE_FACTOR);
// Account for arrowHeight in new window frame.
// Note: we always leave room for the arrow, even if it currently set to
// not be shown. This is so it can easily be toggled whilst the window
// is visible, without altering the window's frame origin point.
float scaledArrowHeight = arrowHeight * MAATTACHEDWINDOW_SCALE_FACTOR;
switch (_side) {
case MAPositionLeft:
case MAPositionLeftTop:
case MAPositionLeftBottom:
contentRect.size.width += scaledArrowHeight;
break;
case MAPositionRight:
case MAPositionRightTop:
case MAPositionRightBottom:
_viewFrame.origin.x += scaledArrowHeight;
contentRect.size.width += scaledArrowHeight;
break;
case MAPositionTop:
case MAPositionTopLeft:
case MAPositionTopRight:
_viewFrame.origin.y += scaledArrowHeight;
contentRect.size.height += scaledArrowHeight;
break;
case MAPositionBottom:
case MAPositionBottomLeft:
case MAPositionBottomRight:
contentRect.size.height += scaledArrowHeight;
break;
default:
break; // won't happen, but this satisfies gcc with -Wall
}
// Position frame origin appropriately for _side, accounting for arrow-inset.
contentRect.origin = (_window) ? [_window convertBaseToScreen:_point] : _point;
float arrowInset = [self _arrowInset];
float halfWidth = contentRect.size.width / 2.0;
float halfHeight = contentRect.size.height / 2.0;
switch (_side) {
case MAPositionTopLeft:
contentRect.origin.x -= contentRect.size.width - arrowInset;
break;
case MAPositionTop:
contentRect.origin.x -= halfWidth;
break;
case MAPositionTopRight:
contentRect.origin.x -= arrowInset;
break;
case MAPositionBottomLeft:
contentRect.origin.y -= contentRect.size.height;
contentRect.origin.x -= contentRect.size.width - arrowInset;
break;
case MAPositionBottom:
contentRect.origin.y -= contentRect.size.height;
contentRect.origin.x -= halfWidth;
break;
case MAPositionBottomRight:
contentRect.origin.x -= arrowInset;
contentRect.origin.y -= contentRect.size.height;
break;
case MAPositionLeftTop:
contentRect.origin.x -= contentRect.size.width;
contentRect.origin.y -= arrowInset;
break;
case MAPositionLeft:
contentRect.origin.x -= contentRect.size.width;
contentRect.origin.y -= halfHeight;
break;
case MAPositionLeftBottom:
contentRect.origin.x -= contentRect.size.width;
contentRect.origin.y -= contentRect.size.height - arrowInset;
break;
case MAPositionRightTop:
contentRect.origin.y -= arrowInset;
break;
case MAPositionRight:
contentRect.origin.y -= halfHeight;
break;
case MAPositionRightBottom:
contentRect.origin.y -= contentRect.size.height - arrowInset;
break;
default:
break; // won't happen, but this satisfies gcc with -Wall
}
// Account for _distance in new window frame.
switch (_side) {
case MAPositionLeft:
case MAPositionLeftTop:
case MAPositionLeftBottom:
contentRect.origin.x -= _distance;
break;
case MAPositionRight:
case MAPositionRightTop:
case MAPositionRightBottom:
contentRect.origin.x += _distance;
break;
case MAPositionTop:
case MAPositionTopLeft:
case MAPositionTopRight:
contentRect.origin.y += _distance;
break;
case MAPositionBottom:
case MAPositionBottomLeft:
case MAPositionBottomRight:
contentRect.origin.y -= _distance;
break;
default:
break; // won't happen, but this satisfies gcc with -Wall
}
// Reconfigure window and view frames appropriately.
[self setFrame:contentRect display:NO];
[_view setFrame:_viewFrame];
}
- (MAWindowPosition)_bestSideForAutomaticPosition
{
// Get all relevant geometry in screen coordinates.
NSRect screenFrame;
if (_window && [_window screen]) {
screenFrame = [[_window screen] visibleFrame];
} else {
screenFrame = [[NSScreen mainScreen] visibleFrame];
}
NSPoint pointOnScreen = (_window) ? [_window convertBaseToScreen:_point] : _point;
NSSize viewSize = [_view frame].size;
viewSize.width += (viewMargin * MAATTACHEDWINDOW_SCALE_FACTOR) * 2.0;
viewSize.height += (viewMargin * MAATTACHEDWINDOW_SCALE_FACTOR) * 2.0;
MAWindowPosition side = MAPositionBottom; // By default, position us centered below.
float scaledArrowHeight = (arrowHeight * MAATTACHEDWINDOW_SCALE_FACTOR) + _distance;
// We'd like to display directly below the specified point, since this gives a
// sense of a relationship between the point and this window. Check there's room.
if (pointOnScreen.y - viewSize.height - scaledArrowHeight < NSMinY(screenFrame)) {
// We'd go off the bottom of the screen. Try the right.
if (pointOnScreen.x + viewSize.width + scaledArrowHeight >= NSMaxX(screenFrame)) {
// We'd go off the right of the screen. Try the left.
if (pointOnScreen.x - viewSize.width - scaledArrowHeight < NSMinX(screenFrame)) {
// We'd go off the left of the screen. Try the top.
if (pointOnScreen.y + viewSize.height + scaledArrowHeight < NSMaxY(screenFrame)) {
side = MAPositionTop;
}
} else {
side = MAPositionLeft;
}
} else {
side = MAPositionRight;
}
}
float halfWidth = viewSize.width / 2.0;
float halfHeight = viewSize.height / 2.0;
NSRect parentFrame = (_window) ? [_window frame] : screenFrame;
float arrowInset = [self _arrowInset];
// We're currently at a primary side.
// Try to avoid going outwith the parent area in the secondary dimension,
// by checking to see if an appropriate corner side would be better.
switch (side) {
case MAPositionBottom:
case MAPositionTop:
// Check to see if we go beyond the left edge of the parent area.
if (pointOnScreen.x - halfWidth < NSMinX(parentFrame)) {
// We go beyond the left edge. Try using right position.
if (pointOnScreen.x + viewSize.width - arrowInset < NSMaxX(screenFrame)) {
// We'd still be on-screen using right, so use it.
if (side == MAPositionBottom) {
side = MAPositionBottomRight;
} else {
side = MAPositionTopRight;
}
}
} else if (pointOnScreen.x + halfWidth >= NSMaxX(parentFrame)) {
// We go beyond the right edge. Try using left position.
if (pointOnScreen.x - viewSize.width + arrowInset >= NSMinX(screenFrame)) {
// We'd still be on-screen using left, so use it.
if (side == MAPositionBottom) {
side = MAPositionBottomLeft;
} else {
side = MAPositionTopLeft;
}
}
}
break;
case MAPositionRight:
case MAPositionLeft:
// Check to see if we go beyond the bottom edge of the parent area.
if (pointOnScreen.y - halfHeight < NSMinY(parentFrame)) {
// We go beyond the bottom edge. Try using top position.
if (pointOnScreen.y + viewSize.height - arrowInset < NSMaxY(screenFrame)) {
// We'd still be on-screen using top, so use it.
if (side == MAPositionRight) {
side = MAPositionRightTop;
} else {
side = MAPositionLeftTop;
}
}
} else if (pointOnScreen.y + halfHeight >= NSMaxY(parentFrame)) {
// We go beyond the top edge. Try using bottom position.
if (pointOnScreen.y - viewSize.height + arrowInset >= NSMinY(screenFrame)) {
// We'd still be on-screen using bottom, so use it.
if (side == MAPositionRight) {
side = MAPositionRightBottom;
} else {
side = MAPositionLeftBottom;
}
}
}
break;
default:
break; // won't happen, but this satisfies gcc with -Wall
}
return side;
}
- (float)_arrowInset
{
float cornerInset = (drawsRoundCornerBesideArrow) ? cornerRadius : 0;
return (cornerInset + (arrowBaseWidth / 2.0)) * MAATTACHEDWINDOW_SCALE_FACTOR;
}
#pragma mark Drawing
- (void)_updateBackground
{
// Call NSWindow's implementation of -setBackgroundColor: because we override
// it in this class to let us set the entire background image of the window
// as an NSColor patternImage.
NSDisableScreenUpdates();
[super setBackgroundColor:[self _backgroundColorPatternImage]];
if ([self isVisible]) {
[self display];
[self invalidateShadow];
}
NSEnableScreenUpdates();
}
- (NSColor *)_backgroundColorPatternImage
{
NSImage *bg = [[NSImage alloc] initWithSize:[self frame].size];
NSRect bgRect = NSZeroRect;
bgRect.size = [bg size];
[bg lockFocus];
NSBezierPath *bgPath = [self _backgroundPath];
[NSGraphicsContext saveGraphicsState];
[bgPath addClip];
// Draw background.
[_MABackgroundColor set];
[bgPath fill];
// Draw border if appropriate.
if (borderWidth > 0) {
// Double the borderWidth since we're drawing inside the path.
[bgPath setLineWidth:(borderWidth * 2.0) * MAATTACHEDWINDOW_SCALE_FACTOR];
[borderColor set];
[bgPath stroke];
}
[NSGraphicsContext restoreGraphicsState];
[bg unlockFocus];
return [NSColor colorWithPatternImage:[bg autorelease]];
}
- (NSBezierPath *)_backgroundPath
{
/*
Construct path for window background, taking account of:
1. hasArrow
2. _side
3. drawsRoundCornerBesideArrow
4. arrowBaseWidth
5. arrowHeight
6. cornerRadius
*/
float scaleFactor = MAATTACHEDWINDOW_SCALE_FACTOR;
float scaledRadius = cornerRadius * scaleFactor;
float scaledArrowWidth = arrowBaseWidth * scaleFactor;
float halfArrowWidth = scaledArrowWidth / 2.0;
NSRect contentArea = NSInsetRect(_viewFrame,
-viewMargin * scaleFactor,
-viewMargin * scaleFactor);
float minX = ceilf(NSMinX(contentArea) * scaleFactor + 0.5f);
float midX = NSMidX(contentArea) * scaleFactor;
float maxX = floorf(NSMaxX(contentArea) * scaleFactor - 0.5f);
float minY = ceilf(NSMinY(contentArea) * scaleFactor + 0.5f);
float midY = NSMidY(contentArea) * scaleFactor;
float maxY = floorf(NSMaxY(contentArea) * scaleFactor - 0.5f);
NSBezierPath *path = [NSBezierPath bezierPath];
[path setLineJoinStyle:NSRoundLineJoinStyle];
// Begin at top-left. This will be either after the rounded corner, or
// at the top-left point if cornerRadius is zero and/or we're drawing
// the arrow at the top-left or left-top without a rounded corner.
NSPoint currPt = NSMakePoint(minX, maxY);
if (scaledRadius > 0 &&
(drawsRoundCornerBesideArrow ||
(_side != MAPositionBottomRight && _side != MAPositionRightBottom))
) {
currPt.x += scaledRadius;
}
NSPoint endOfLine = NSMakePoint(maxX, maxY);
BOOL shouldDrawNextCorner = NO;
if (scaledRadius > 0 &&
(drawsRoundCornerBesideArrow ||
(_side != MAPositionBottomLeft && _side != MAPositionLeftBottom))
) {
endOfLine.x -= scaledRadius;
shouldDrawNextCorner = YES;
}
[path moveToPoint:currPt];
// If arrow should be drawn at top-left point, draw it.
if (_side == MAPositionBottomRight) {
[self _appendArrowToPath:path];
} else if (_side == MAPositionBottom) {
// Line to relevant point before arrow.
[path lineToPoint:NSMakePoint(midX - halfArrowWidth, maxY)];
// Draw arrow.
[self _appendArrowToPath:path];
} else if (_side == MAPositionBottomLeft) {
// Line to relevant point before arrow.
[path lineToPoint:NSMakePoint(endOfLine.x - scaledArrowWidth, maxY)];
// Draw arrow.
[self _appendArrowToPath:path];
}
// Line to end of this side.
[path lineToPoint:endOfLine];
// Rounded corner on top-right.
if (shouldDrawNextCorner) {
[path appendBezierPathWithArcFromPoint:NSMakePoint(maxX, maxY)
toPoint:NSMakePoint(maxX, maxY - scaledRadius)
radius:scaledRadius];
}
// Draw the right side, beginning at the top-right.
endOfLine = NSMakePoint(maxX, minY);
shouldDrawNextCorner = NO;
if (scaledRadius > 0 &&
(drawsRoundCornerBesideArrow ||
(_side != MAPositionTopLeft && _side != MAPositionLeftTop))
) {
endOfLine.y += scaledRadius;
shouldDrawNextCorner = YES;
}
// If arrow should be drawn at right-top point, draw it.
if (_side == MAPositionLeftBottom) {
[self _appendArrowToPath:path];
} else if (_side == MAPositionLeft) {
// Line to relevant point before arrow.
[path lineToPoint:NSMakePoint(maxX, midY + halfArrowWidth)];
// Draw arrow.
[self _appendArrowToPath:path];
} else if (_side == MAPositionLeftTop) {
// Line to relevant point before arrow.
[path lineToPoint:NSMakePoint(maxX, endOfLine.y + scaledArrowWidth)];
// Draw arrow.
[self _appendArrowToPath:path];
}
// Line to end of this side.
[path lineToPoint:endOfLine];
// Rounded corner on bottom-right.
if (shouldDrawNextCorner) {
[path appendBezierPathWithArcFromPoint:NSMakePoint(maxX, minY)
toPoint:NSMakePoint(maxX - scaledRadius, minY)
radius:scaledRadius];
}
// Draw the bottom side, beginning at the bottom-right.
endOfLine = NSMakePoint(minX, minY);
shouldDrawNextCorner = NO;
if (scaledRadius > 0 &&
(drawsRoundCornerBesideArrow ||
(_side != MAPositionTopRight && _side != MAPositionRightTop))
) {
endOfLine.x += scaledRadius;
shouldDrawNextCorner = YES;
}
// If arrow should be drawn at bottom-right point, draw it.
if (_side == MAPositionTopLeft) {
[self _appendArrowToPath:path];
} else if (_side == MAPositionTop) {
// Line to relevant point before arrow.
[path lineToPoint:NSMakePoint(midX + halfArrowWidth, minY)];
// Draw arrow.
[self _appendArrowToPath:path];
} else if (_side == MAPositionTopRight) {
// Line to relevant point before arrow.
[path lineToPoint:NSMakePoint(endOfLine.x + scaledArrowWidth, minY)];
// Draw arrow.
[self _appendArrowToPath:path];
}
// Line to end of this side.
[path lineToPoint:endOfLine];
// Rounded corner on bottom-left.
if (shouldDrawNextCorner) {
[path appendBezierPathWithArcFromPoint:NSMakePoint(minX, minY)
toPoint:NSMakePoint(minX, minY + scaledRadius)
radius:scaledRadius];
}
// Draw the left side, beginning at the bottom-left.
endOfLine = NSMakePoint(minX, maxY);
shouldDrawNextCorner = NO;
if (scaledRadius > 0 &&
(drawsRoundCornerBesideArrow ||
(_side != MAPositionRightBottom && _side != MAPositionBottomRight))
) {
endOfLine.y -= scaledRadius;
shouldDrawNextCorner = YES;
}
// If arrow should be drawn at left-bottom point, draw it.
if (_side == MAPositionRightTop) {
[self _appendArrowToPath:path];
} else if (_side == MAPositionRight) {
// Line to relevant point before arrow.
[path lineToPoint:NSMakePoint(minX, midY - halfArrowWidth)];
// Draw arrow.
[self _appendArrowToPath:path];
} else if (_side == MAPositionRightBottom) {
// Line to relevant point before arrow.
[path lineToPoint:NSMakePoint(minX, endOfLine.y - scaledArrowWidth)];
// Draw arrow.
[self _appendArrowToPath:path];
}
// Line to end of this side.
[path lineToPoint:endOfLine];
// Rounded corner on top-left.
if (shouldDrawNextCorner) {
[path appendBezierPathWithArcFromPoint:NSMakePoint(minX, maxY)
toPoint:NSMakePoint(minX + scaledRadius, maxY)
radius:scaledRadius];
}
[path closePath];
return path;
}
- (void)_appendArrowToPath:(NSBezierPath *)path
{
if (!hasArrow) {
return;
}
float scaleFactor = MAATTACHEDWINDOW_SCALE_FACTOR;
float scaledArrowWidth = arrowBaseWidth * scaleFactor;
float halfArrowWidth = scaledArrowWidth / 2.0;
float scaledArrowHeight = arrowHeight * scaleFactor;
NSPoint currPt = [path currentPoint];
NSPoint tipPt = currPt;
NSPoint endPt = currPt;
// Note: we always build the arrow path in a clockwise direction.
switch (_side) {
case MAPositionLeft:
case MAPositionLeftTop:
case MAPositionLeftBottom:
// Arrow points towards right. We're starting from the top.
tipPt.x += scaledArrowHeight;
tipPt.y -= halfArrowWidth;
endPt.y -= scaledArrowWidth;
break;
case MAPositionRight:
case MAPositionRightTop:
case MAPositionRightBottom:
// Arrow points towards left. We're starting from the bottom.
tipPt.x -= scaledArrowHeight;
tipPt.y += halfArrowWidth;
endPt.y += scaledArrowWidth;
break;
case MAPositionTop:
case MAPositionTopLeft:
case MAPositionTopRight:
// Arrow points towards bottom. We're starting from the right.
tipPt.y -= scaledArrowHeight;
tipPt.x -= halfArrowWidth;
endPt.x -= scaledArrowWidth;
break;
case MAPositionBottom:
case MAPositionBottomLeft:
case MAPositionBottomRight:
// Arrow points towards top. We're starting from the left.
tipPt.y += scaledArrowHeight;
tipPt.x += halfArrowWidth;
endPt.x += scaledArrowWidth;
break;
default:
break; // won't happen, but this satisfies gcc with -Wall
}
[path lineToPoint:tipPt];
[path lineToPoint:endPt];
}
- (void)_redisplay
{
if (_resizing) {
return;
}
_resizing = YES;
NSDisableScreenUpdates();
[self _updateGeometry];
[self _updateBackground];
NSEnableScreenUpdates();
_resizing = NO;
}
# pragma mark Window Behaviour
- (BOOL)canBecomeMainWindow
{
return NO;
}
- (BOOL)canBecomeKeyWindow
{
return YES;
}
- (BOOL)isExcludedFromWindowsMenu
{
return YES;
}
- (BOOL)validateMenuItem:(NSMenuItem *)item
{
if (_window) {
return [_window validateMenuItem:item];
}
return [super validateMenuItem:item];
}
- (IBAction)performClose:(id)sender
{
if (_window) {
[_window performClose:sender];
} else {
[super performClose:sender];
}
}
# pragma mark Notification handlers
- (void)windowDidResize:(NSNotification *)note
{
[self _redisplay];
}
#pragma mark Accessors
- (void)setPoint:(NSPoint)point side:(MAWindowPosition)side
{
// Thanks to Martin Redington.
_point = point;
_side = side;
NSDisableScreenUpdates();
[self _updateGeometry];
[self _updateBackground];
NSEnableScreenUpdates();
}
- (NSColor *)windowBackgroundColor {
return [[_MABackgroundColor retain] autorelease];
}
- (void)setBackgroundColor:(NSColor *)value {
if (_MABackgroundColor != value) {
[_MABackgroundColor release];
_MABackgroundColor = [value copy];
[self _updateBackground];
}
}
- (NSColor *)borderColor {
return [[borderColor retain] autorelease];
}
- (void)setBorderColor:(NSColor *)value {
if (borderColor != value) {
[borderColor release];
borderColor = [value copy];
[self _updateBackground];
}
}
- (float)borderWidth {
return borderWidth;
}
- (void)setBorderWidth:(float)value {
if (borderWidth != value) {
float maxBorderWidth = viewMargin;
if (value <= maxBorderWidth) {
borderWidth = value;
} else {
borderWidth = maxBorderWidth;
}
[self _updateBackground];
}
}
- (float)viewMargin {
return viewMargin;
}
- (void)setViewMargin:(float)value {
if (viewMargin != value) {
viewMargin = MAX(value, 0.0);
// Adjust cornerRadius appropriately (which will also adjust arrowBaseWidth).
[self setCornerRadius:cornerRadius];
}
}
- (float)arrowBaseWidth {
return arrowBaseWidth;
}
- (void)setArrowBaseWidth:(float)value {
float maxWidth = (MIN(_viewFrame.size.width, _viewFrame.size.height) +
(viewMargin * 2.0)) - cornerRadius;
if (drawsRoundCornerBesideArrow) {
maxWidth -= cornerRadius;
}
if (value <= maxWidth) {
arrowBaseWidth = value;
} else {
arrowBaseWidth = maxWidth;
}
[self _redisplay];
}
- (float)arrowHeight {
return arrowHeight;
}
- (void)setArrowHeight:(float)value {
if (arrowHeight != value) {
arrowHeight = value;
[self _redisplay];
}
}
- (float)hasArrow {
return hasArrow;
}
- (void)setHasArrow:(float)value {
if (hasArrow != value) {
hasArrow = value;
[self _updateBackground];
}
}
- (float)cornerRadius {
return cornerRadius;
}
- (void)setCornerRadius:(float)value {
float maxRadius = ((MIN(_viewFrame.size.width, _viewFrame.size.height) +
(viewMargin * 2.0)) - arrowBaseWidth) / 2.0;
if (value <= maxRadius) {
cornerRadius = value;
} else {
cornerRadius = maxRadius;
}
cornerRadius = MAX(cornerRadius, 0.0);
// Adjust arrowBaseWidth appropriately.
[self setArrowBaseWidth:arrowBaseWidth];
}
- (float)drawsRoundCornerBesideArrow {
return drawsRoundCornerBesideArrow;
}
- (void)setDrawsRoundCornerBesideArrow:(float)value {
if (drawsRoundCornerBesideArrow != value) {
drawsRoundCornerBesideArrow = value;
[self _redisplay];
}
}
- (void)setBackgroundImage:(NSImage *)value
{
if (value) {
[self setBackgroundColor:[NSColor colorWithPatternImage:value]];
}
}
@end