summaryrefslogtreecommitdiffstats
path: root/chrome/browser/ui/cocoa/profile_menu_button.mm
diff options
context:
space:
mode:
Diffstat (limited to 'chrome/browser/ui/cocoa/profile_menu_button.mm')
-rw-r--r--chrome/browser/ui/cocoa/profile_menu_button.mm395
1 files changed, 395 insertions, 0 deletions
diff --git a/chrome/browser/ui/cocoa/profile_menu_button.mm b/chrome/browser/ui/cocoa/profile_menu_button.mm
new file mode 100644
index 0000000..866bb40
--- /dev/null
+++ b/chrome/browser/ui/cocoa/profile_menu_button.mm
@@ -0,0 +1,395 @@
+// Copyright (c) 2011 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#import "chrome/browser/ui/cocoa/profile_menu_button.h"
+
+#include <algorithm>
+
+#include "base/logging.h"
+#import "third_party/GTM/AppKit/GTMFadeTruncatingTextFieldCell.h"
+
+namespace {
+
+const CGFloat kTabWidth = 24;
+const CGFloat kTabHeight = 13;
+const CGFloat kTabArrowWidth = 7;
+const CGFloat kTabArrowHeight = 4;
+const CGFloat kTabRoundRectRadius = 5;
+const CGFloat kTabDisplayNameMarginY = 3;
+
+NSColor* GetWhiteWithAlpha(CGFloat alpha) {
+ return [NSColor colorWithCalibratedWhite:1.0 alpha:alpha];
+}
+
+NSColor* GetBlackWithAlpha(CGFloat alpha) {
+ return [NSColor colorWithCalibratedWhite:0.0 alpha:alpha];
+}
+
+} // namespace
+
+@interface ProfileMenuButton (Private)
+- (void)commonInit;
+- (NSPoint)popUpMenuPosition;
+- (NSImage*)tabImage;
+- (NSRect)textFieldRect;
+- (NSBezierPath*)tabPathWithRect:(NSRect)rect
+ radius:(CGFloat)radius;
+- (NSBezierPath*)downArrowPathWithRect:(NSRect)rect;
+- (NSImage*)tabImageWithSize:(NSSize)tabSize
+ fillColor:(NSColor*)fillColor
+ isPressed:(BOOL)isPressed;
+@end
+
+@implementation ProfileMenuButton
+
+@synthesize shouldShowProfileDisplayName = shouldShowProfileDisplayName_;
+
+- (void)commonInit {
+ textFieldCell_.reset(
+ [[GTMFadeTruncatingTextFieldCell alloc] initTextCell:@""]);
+ [textFieldCell_ setBackgroundStyle:NSBackgroundStyleRaised];
+ [textFieldCell_ setAlignment:NSRightTextAlignment];
+ [textFieldCell_ setFont:[NSFont systemFontOfSize:
+ [NSFont smallSystemFontSize]]];
+}
+
+- (id)initWithFrame:(NSRect)frame
+ pullsDown:(BOOL)flag {
+ if ((self = [super initWithFrame:frame pullsDown:flag]))
+ [self commonInit];
+ return self;
+}
+
+- (id)initWithCoder:(NSCoder*)decoder {
+ if ((self = [super initWithCoder:decoder]))
+ [self commonInit];
+ return self;
+}
+
+- (void)dealloc {
+ [[NSNotificationCenter defaultCenter] removeObserver:self];
+ [super dealloc];
+}
+
+- (NSString*)profileDisplayName {
+ return [textFieldCell_ stringValue];
+}
+
+- (void)setProfileDisplayName:(NSString*)name {
+ if (![[textFieldCell_ stringValue] isEqual:name]) {
+ [textFieldCell_ setStringValue:name];
+ [self setNeedsDisplay:YES];
+ }
+}
+
+- (void)setShouldShowProfileDisplayName:(BOOL)flag {
+ shouldShowProfileDisplayName_ = flag;
+ [self setNeedsDisplay:YES];
+}
+
+- (BOOL)isFlipped {
+ return NO;
+}
+
+- (void)viewWillMoveToWindow:(NSWindow*)newWindow {
+ if ([self window] == newWindow)
+ return;
+
+ if ([self window]) {
+ [[NSNotificationCenter defaultCenter]
+ removeObserver:self
+ name:NSWindowDidBecomeMainNotification
+ object:[self window]];
+ [[NSNotificationCenter defaultCenter]
+ removeObserver:self
+ name:NSWindowDidResignMainNotification
+ object:[self window]];
+ }
+
+ if (newWindow) {
+ [[NSNotificationCenter defaultCenter]
+ addObserver:self
+ selector:@selector(onWindowFocusChanged:)
+ name:NSWindowDidBecomeMainNotification
+ object:newWindow];
+ [[NSNotificationCenter defaultCenter]
+ addObserver:self
+ selector:@selector(onWindowFocusChanged:)
+ name:NSWindowDidResignMainNotification
+ object:newWindow];
+ }
+}
+
+- (void)onWindowFocusChanged:(NSNotification*)note {
+ [self setNeedsDisplay:YES];
+}
+
+- (NSRect)tabRect {
+ NSRect bounds = [self bounds];
+ NSRect tabRect;
+ tabRect.size.width = kTabWidth;
+ tabRect.size.height = kTabHeight;
+ tabRect.origin.x = NSMaxX(bounds) - NSWidth(tabRect);
+ tabRect.origin.y = NSMaxY(bounds) - NSHeight(tabRect);
+ return tabRect;
+}
+
+- (NSRect)textFieldRect {
+ NSRect bounds = [self bounds];
+ NSSize desiredSize = [textFieldCell_ cellSize];
+
+ NSRect textRect = bounds;
+ textRect.size.height = std::min(desiredSize.height, NSHeight(bounds));
+
+ // For some reason there's always a 2 pixel gap on the right side of the
+ // text field. Fix it by moving the text field to the right by 2 pixels.
+ textRect.origin.x += 2;
+
+ return textRect;
+}
+
+- (NSView*)hitTest:(NSPoint)aPoint {
+ NSView* probe = [super hitTest:aPoint];
+ if (probe != self)
+ return probe;
+
+ NSPoint viewPoint = [self convertPoint:aPoint fromView:[self superview]];
+ BOOL isFlipped = [self isFlipped];
+ if (NSMouseInRect(viewPoint, [self tabRect], isFlipped))
+ return self;
+ else
+ return nil;
+}
+
+- (NSBezierPath*)tabPathWithRect:(NSRect)rect
+ radius:(CGFloat)radius {
+ const NSRect innerRect = NSInsetRect(rect, radius, radius);
+ NSBezierPath* path = [NSBezierPath bezierPath];
+
+ // Top left
+ [path moveToPoint:NSMakePoint(NSMinX(rect), NSMaxY(rect))];
+
+ // Bottom left
+ [path lineToPoint:NSMakePoint(NSMinX(rect), NSMinY(innerRect))];
+ [path appendBezierPathWithArcWithCenter:NSMakePoint(NSMinX(innerRect),
+ NSMinY(innerRect))
+ radius:radius
+ startAngle:180
+ endAngle:270
+ clockwise:NO];
+
+ // Bottom right
+ [path lineToPoint:NSMakePoint(NSMaxX(innerRect), NSMinY(rect))];
+ [path appendBezierPathWithArcWithCenter:NSMakePoint(NSMaxX(innerRect),
+ NSMinY(innerRect))
+ radius:radius
+ startAngle:270
+ endAngle:360
+ clockwise:NO];
+
+ // Top right
+ [path lineToPoint:NSMakePoint(NSMaxX(rect), NSMaxY(rect))];
+
+ [path closePath];
+ return path;
+}
+
+- (NSBezierPath*)downArrowPathWithRect:(NSRect)rect {
+ NSBezierPath* path = [NSBezierPath bezierPath];
+
+ // Top left
+ [path moveToPoint:NSMakePoint(NSMinX(rect), NSMaxY(rect))];
+
+ // Bottom middle
+ [path lineToPoint:NSMakePoint(NSMidX(rect), NSMinY(rect))];
+
+ // Top right
+ [path lineToPoint:NSMakePoint(NSMaxX(rect), NSMaxY(rect))];
+
+ [path closePath];
+ return path;
+}
+
+- (NSImage*)tabImageWithSize:(NSSize)tabSize
+ fillColor:(NSColor*)fillColor
+ isPressed:(BOOL)isPressed {
+ NSImage* image = [[[NSImage alloc] initWithSize:tabSize] autorelease];
+ [image lockFocus];
+
+ // White shadow for inset look
+ [[NSGraphicsContext currentContext] saveGraphicsState];
+ scoped_nsobject<NSShadow> tabShadow([[NSShadow alloc] init]);
+ [tabShadow.get() setShadowOffset:NSMakeSize(0, -1)];
+ [tabShadow setShadowBlurRadius:0];
+ [tabShadow.get() setShadowColor:GetWhiteWithAlpha(0.6)];
+ [tabShadow set];
+
+ // Gray outline
+ NSRect tabRect = NSMakeRect(0, 1, tabSize.width, tabSize.height - 1);
+ NSBezierPath* outlinePath = [self tabPathWithRect:tabRect
+ radius:kTabRoundRectRadius];
+ [[NSColor colorWithCalibratedWhite:0.44 alpha:1.0] set];
+ [outlinePath fill];
+
+ [[NSGraphicsContext currentContext] restoreGraphicsState];
+
+ // Fill
+ NSRect fillRect = NSInsetRect(tabRect, 1, 0);
+ fillRect.size.height -= 1;
+ fillRect.origin.y += 1;
+ NSBezierPath* fillPath = [self tabPathWithRect:fillRect
+ radius:kTabRoundRectRadius - 1];
+ [fillColor set];
+ [fillPath fill];
+
+ // Shading for fill to make the bottom of the tab slightly darker.
+ scoped_nsobject<NSGradient> gradient([[NSGradient alloc]
+ initWithStartingColor:GetBlackWithAlpha(isPressed ? 0.2 : 0.0)
+ endingColor:GetBlackWithAlpha(0.2)]);
+ [gradient drawInBezierPath:fillPath angle:270];
+
+ // Highlight on top
+ NSRect highlightRect = NSInsetRect(tabRect, 1, 0);
+ highlightRect.size.height = 1;
+ highlightRect.origin.y = NSMaxY(tabRect) - highlightRect.size.height;
+ [GetWhiteWithAlpha(0.5) set];
+ NSRectFillUsingOperation(highlightRect, NSCompositeSourceOver);
+
+ // Arrow shadow
+ [[NSGraphicsContext currentContext] saveGraphicsState];
+ scoped_nsobject<NSShadow> arrowShadow([[NSShadow alloc] init]);
+ [arrowShadow.get() setShadowOffset:NSMakeSize(0, -1)];
+ [arrowShadow setShadowBlurRadius:0];
+ [arrowShadow.get() setShadowColor:GetBlackWithAlpha(0.6)];
+ [arrowShadow set];
+
+ // Down arrow
+ NSRect arrowRect;
+ arrowRect.size.width = kTabArrowWidth;
+ arrowRect.size.height = kTabArrowHeight;
+ arrowRect.origin.x = NSMinX(tabRect) + roundf((NSWidth(tabRect) -
+ NSWidth(arrowRect)) / 2.0);
+ arrowRect.origin.y = NSMinY(tabRect) + roundf((tabRect.size.height -
+ arrowRect.size.height) / 2.0);
+ NSBezierPath* arrowPath = [self downArrowPathWithRect:arrowRect];
+ if (isPressed)
+ [[NSColor colorWithCalibratedWhite:0.8 alpha:1.0] set];
+ else
+ [[NSColor whiteColor] set];
+ [arrowPath fill];
+
+ [[NSGraphicsContext currentContext] restoreGraphicsState];
+
+ [image unlockFocus];
+ return image;
+}
+
+- (NSImage*)tabImage {
+ BOOL isPressed = [[self cell] isHighlighted];
+
+ // Invalidate the cached image if necessary.
+ if (cachedTabImageIsPressed_ != isPressed) {
+ cachedTabImageIsPressed_ = isPressed;
+ cachedTabImage_.reset();
+ }
+
+ if (cachedTabImage_)
+ return cachedTabImage_;
+
+ // TODO: Use different colors for different profiles and tint for
+ // the current browser theme.
+ NSColor* fillColor = [NSColor colorWithCalibratedRed:122.0/255.0
+ green:177.0/255.0
+ blue:252.0/255.0
+ alpha:1.0];
+ NSRect tabRect = [self tabRect];
+ cachedTabImage_.reset([[self tabImageWithSize:tabRect.size
+ fillColor:fillColor
+ isPressed:isPressed] retain]);
+ return cachedTabImage_;
+}
+
+- (void)drawRect:(NSRect)rect {
+ CGFloat alpha = [[self window] isMainWindow] ? 1.0 : 0.5;
+ [[self tabImage] drawInRect:[self tabRect]
+ fromRect:NSZeroRect
+ operation:NSCompositeSourceOver
+ fraction:alpha];
+
+ if (shouldShowProfileDisplayName_) {
+ NSColor* textColor = [[self window] isMainWindow] ?
+ GetBlackWithAlpha(0.6) : GetBlackWithAlpha(0.4);
+ if (![[textFieldCell_ textColor] isEqual:textColor])
+ [textFieldCell_ setTextColor:textColor];
+ [textFieldCell_ drawWithFrame:[self textFieldRect] inView:self];
+ }
+}
+
+- (NSPoint)popUpMenuPosition {
+ NSPoint menuPos = [self tabRect].origin;
+ // By default popUpContextMenu: causes the menu to show up a few pixels above
+ // the point you give it. We need to shift it down a bit so that it lines up
+ // with the bottom of the tab.
+ menuPos.y -= 6;
+ return [self convertPoint:menuPos toView:nil];
+}
+
+- (void) mouseDown:(NSEvent*)event
+ withShowMenuTarget:(id)target {
+ if (![self menu]) {
+ [super mouseDown:event];
+ return;
+ }
+
+ NSPoint point = [[self superview]
+ convertPointFromBase:[event locationInWindow]];
+ if (![[self hitTest:point] isEqual:self])
+ return;
+
+ // Draw the control as depressed.
+ [self highlight:YES];
+
+ NSEvent* fakeEvent = [NSEvent
+ mouseEventWithType:[event type]
+ location:[self popUpMenuPosition]
+ modifierFlags:[event modifierFlags]
+ timestamp:[event timestamp]
+ windowNumber:[event windowNumber]
+ context:[event context]
+ eventNumber:[event eventNumber]
+ clickCount:[event clickCount]
+ pressure:[event pressure]];
+ DCHECK([target respondsToSelector:
+ @selector(popUpContextMenu:withEvent:forView:)]);
+ [target popUpContextMenu:[self menu]
+ withEvent:fakeEvent
+ forView:self];
+
+ [self highlight:NO];
+}
+
+- (void)mouseDown:(NSEvent*)event {
+ [self mouseDown:event
+ withShowMenuTarget:[NSMenu class]];
+}
+
+- (NSSize)desiredControlSize {
+ NSSize size = [self tabRect].size;
+
+ if (shouldShowProfileDisplayName_) {
+ NSSize textFieldSize = [textFieldCell_ cellSize];
+ size.width = std::max(size.width, textFieldSize.width);
+ size.height += textFieldSize.height + kTabDisplayNameMarginY;
+ }
+
+ size.width = ceil(size.width);
+ size.height = ceil(size.height);
+ return size;
+}
+
+- (NSSize)minControlSize {
+ return [self tabRect].size;
+}
+
+@end