123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951 |
- //
- // 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
|