blob: adb6b6034c2328c170fe7f28eaf8a8cb6c77c734 [file] [log] [blame]
// Copyright 2014 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/website_settings/split_block_button.h"
#include <cmath>
#include "base/logging.h"
#include "base/mac/scoped_nsobject.h"
#include "chrome/grit/generated_resources.h"
#include "skia/ext/skia_utils_mac.h"
#import "ui/base/cocoa/menu_controller.h"
#include "ui/base/l10n/l10n_util_mac.h"
#include "ui/base/models/simple_menu_model.h"
namespace {
enum MouseLocation {
kInsideLeftCell,
kInsideRightCell,
kNotInside,
};
enum CornerType {
kRounded,
kAngled,
};
NSBezierPath* PathWithCornerStyles(NSRect frame,
CornerType leftCornerStyle,
CornerType rightCornerStyle) {
base::scoped_nsobject<NSBezierPath> path([[NSBezierPath bezierPath] retain]);
const CGFloat x0 = NSMinX(frame);
const CGFloat x1 = NSMaxX(frame);
const CGFloat y0 = NSMinY(frame);
const CGFloat y1 = NSMaxY(frame);
const CGFloat radius = 2;
// Start at the center bottom. Draw left and up, including both left corners.
[path moveToPoint:NSMakePoint(std::floor((x1 - x0) * .5), y0)];
if (leftCornerStyle == kAngled) {
[path lineToPoint:NSMakePoint(x0, y0)];
[path lineToPoint:NSMakePoint(x0, y1)];
} else {
[path appendBezierPathWithArcFromPoint:NSMakePoint(x0, y0)
toPoint:NSMakePoint(x0, y0 + radius)
radius:radius];
[path appendBezierPathWithArcFromPoint:NSMakePoint(x0, y1)
toPoint:NSMakePoint(x0 + radius, y1)
radius:radius];
}
// Draw through the upper right-hand and lower-right-hand corners.
if (rightCornerStyle == kAngled) {
[path lineToPoint:NSMakePoint(x1, y1)];
[path lineToPoint:NSMakePoint(x1, y0)];
} else {
[path appendBezierPathWithArcFromPoint:NSMakePoint(x1, y1)
toPoint:NSMakePoint(x1, y1 - radius)
radius:radius];
[path appendBezierPathWithArcFromPoint:NSMakePoint(x1, y0)
toPoint:NSMakePoint(x1 - radius, y0)
radius:radius];
}
return path.autorelease();
}
void DrawBezel(id<ConstrainedWindowButtonDrawableCell>cell,
CornerType leftCorners,
CornerType rightCorners,
NSRect frame,
NSView* view) {
if ([cell isMouseInside]) {
base::scoped_nsobject<NSBezierPath> path(
[PathWithCornerStyles(frame, leftCorners, rightCorners) retain]);
[ConstrainedWindowButton DrawBackgroundAndShadowForPath:path
withCell:cell
inView:view];
[ConstrainedWindowButton DrawInnerHighlightForPath:path
withCell:cell
inView:view];
}
}
} // namespace
// A button cell used by SplitBlockButton, containing the title.
@interface SplitButtonTitleCell : ConstrainedWindowButtonCell
- (NSRect)rect;
@end
// A button cell used by SplitBlockButton, containing the popup menu.
@interface SplitButtonPopUpCell :
NSPopUpButtonCell<ConstrainedWindowButtonDrawableCell> {
@private
BOOL isMouseInside_;
base::scoped_nsobject<MenuController> menuController_;
scoped_ptr<ui::SimpleMenuModel> menuModel_;
}
// Designated initializer.
- (id)initWithMenuDelegate:(ui::SimpleMenuModel::Delegate*)menuDelegate;
- (NSRect)rect;
@end
@implementation SplitBlockButton
- (id)initWithMenuDelegate:(ui::SimpleMenuModel::Delegate*)menuDelegate {
if (self = [super initWithFrame:NSZeroRect]) {
leftCell_.reset([[SplitButtonTitleCell alloc] init]);
rightCell_.reset([[SplitButtonPopUpCell alloc]
initWithMenuDelegate:menuDelegate]);
[leftCell_ setTitle:l10n_util::GetNSString(IDS_PERMISSION_DENY)];
[leftCell_ setEnabled:YES];
[rightCell_ setEnabled:YES];
}
return self;
}
+ (Class)cellClass {
return nil;
}
- (NSString*)title {
return [leftCell_ title];
}
- (void)setAction:(SEL)action {
[leftCell_ setAction:action];
}
- (void)setTarget:(id)target {
[leftCell_ setTarget:target];
}
- (void)drawRect:(NSRect)rect {
// Copy the base class: inset to leave room for the shadow.
--rect.size.height;
// This function assumes that |rect| is always the same as [self frame].
// If that changes, the drawing functions will need to be adjusted.
const CGFloat radius = 2;
NSBezierPath* path = [NSBezierPath bezierPathWithRoundedRect:rect
xRadius:radius
yRadius:radius];
[ConstrainedWindowButton DrawBackgroundAndShadowForPath:path
withCell:nil
inView:self];
// Use intersection rects for the cell drawing, to ensure the height
// adjustment is honored.
[leftCell_ setControlView:self];
[leftCell_ drawWithFrame:NSIntersectionRect(rect, [self leftCellRect])
inView:self];
[rightCell_ setControlView:self];
[rightCell_ drawWithFrame:NSIntersectionRect(rect, [self rightCellRect])
inView:self];
// Draw the border.
path = [NSBezierPath bezierPathWithRoundedRect:NSInsetRect(rect, 0.5, 0.5)
xRadius:radius
yRadius:radius];
[ConstrainedWindowButton DrawBorderForPath:path
withCell:nil
inView:self];
}
- (void)updateTrackingAreas {
[self updateTrackingArea:&leftTrackingArea_
forCell:leftCell_
withRect:[self leftCellRect]];
[self updateTrackingArea:&rightTrackingArea_
forCell:rightCell_
withRect:[self rightCellRect]];
}
- (void)updateTrackingArea:(ui::ScopedCrTrackingArea*)trackingArea
forCell:(id<ConstrainedWindowButtonDrawableCell>)cell
withRect:(NSRect)rect {
DCHECK(trackingArea);
NSTrackingAreaOptions options = NSTrackingMouseEnteredAndExited |
NSTrackingActiveInActiveApp;
[self removeTrackingArea:trackingArea->get()];
trackingArea->reset([[CrTrackingArea alloc] initWithRect:rect
options:options
owner:self
userInfo:nil]);
[self addTrackingArea:trackingArea->get()];
}
- (void)mouseEntered:(NSEvent*)theEvent {
[self mouseMoved:theEvent];
}
- (void)mouseExited:(NSEvent*)theEvent {
[self mouseMoved:theEvent];
}
- (void)mouseMoved:(NSEvent*)theEvent {
MouseLocation location = [self mouseLocationForEvent:theEvent];
[rightCell_ setIsMouseInside:NO];
[leftCell_ setIsMouseInside:NO];
if (location == kInsideLeftCell)
[leftCell_ setIsMouseInside:YES];
else if (location == kInsideRightCell)
[rightCell_ setIsMouseInside:YES];
[self setNeedsDisplay:YES];
}
- (void)mouseDown:(NSEvent*)theEvent {
MouseLocation downLocation = [self mouseLocationForEvent:theEvent];
NSCell* focusCell = nil;
NSRect rect;
if (downLocation == kInsideLeftCell) {
focusCell = leftCell_.get();
rect = [self leftCellRect];
} else if (downLocation == kInsideRightCell) {
focusCell = rightCell_.get();
rect = [self rightCellRect];
}
do {
MouseLocation location = [self mouseLocationForEvent:theEvent];
if (location != kNotInside) {
[focusCell setHighlighted:YES];
[self setNeedsDisplay:YES];
if ([focusCell trackMouse:theEvent
inRect:rect
ofView:self
untilMouseUp:NO]) {
[focusCell setState:![focusCell state]];
[self setNeedsDisplay:YES];
break;
} else {
// The above -trackMouse call returned NO, so we know that
// the mouse left the cell before a mouse up event occurred.
[focusCell setHighlighted:NO];
[self setNeedsDisplay:YES];
}
}
const NSUInteger mask = NSLeftMouseUpMask | NSLeftMouseDraggedMask;
theEvent = [[self window] nextEventMatchingMask:mask];
} while ([theEvent type] != NSLeftMouseUp);
}
- (MouseLocation)mouseLocationForEvent:(NSEvent*)theEvent {
MouseLocation location = kNotInside;
NSPoint mousePoint = [self convertPoint:[theEvent locationInWindow]
fromView:nil];
if ([self mouse:mousePoint inRect:[leftCell_ rect]])
location = kInsideLeftCell;
else if ([self mouse:mousePoint inRect:[self rightCellRect]])
location = kInsideRightCell;
return location;
}
- (void)sizeToFit {
NSSize leftSize = [leftCell_ cellSize];
NSSize rightSize = [rightCell_ cellSize];
NSSize size = NSMakeSize(
std::ceil(std::max(leftSize.width + rightSize.width,
constrained_window_button::kButtonMinWidth)),
std::ceil(std::max(leftSize.height, rightSize.height)));
[self setFrameSize:size];
}
- (NSRect)leftCellRect {
return [leftCell_ rect];
}
- (NSRect)rightCellRect {
NSRect leftFrame, rightFrame;
NSDivideRect([self bounds], &leftFrame, &rightFrame,
NSWidth([self leftCellRect]), NSMinXEdge);
return rightFrame;
}
// Accessor for Testing.
- (NSMenu*)menu {
return [rightCell_ menu];
}
@end
@implementation SplitButtonTitleCell
- (void)drawBezelWithFrame:(NSRect)frame inView:(NSView *)controlView {
DrawBezel(self, kRounded, kAngled, frame, controlView);
}
- (NSRect)rect {
NSSize size = [self cellSize];
return NSMakeRect(0, 0, std::ceil(size.width), std::ceil(size.height));
}
@end
@implementation SplitButtonPopUpCell
@synthesize isMouseInside = isMouseInside_;
- (id)initWithMenuDelegate:(ui::SimpleMenuModel::Delegate*)menuDelegate {
if (self = [super initTextCell:@"" pullsDown:YES]) {
[self setControlSize:NSSmallControlSize];
[self setArrowPosition:NSPopUpArrowAtCenter];
[self setBordered:NO];
[self setBackgroundColor:[NSColor clearColor]];
menuModel_.reset(new ui::SimpleMenuModel(menuDelegate));
menuModel_->AddItemWithStringId(0, IDS_PERMISSION_CUSTOMIZE);
menuController_.reset(
[[MenuController alloc] initWithModel:menuModel_.get()
useWithPopUpButtonCell:NO]);
[self setMenu:[menuController_ menu]];
[self setUsesItemFromMenu:NO];
}
return self;
}
- (void)drawBorderAndBackgroundWithFrame:(NSRect)frame
inView:(NSView*)controlView {
// The arrow, which is what should be drawn by the base class, is drawn
// during -drawBezelWithFrame. The only way to draw our own border with
// the default arrow is to make the cell unbordered, and draw the border
// from -drawBorderAndBackgroundWithFrame, rather than simply overriding
// -drawBezelWithFrame.
DrawBezel(self, kAngled, kRounded, frame, controlView);
[super drawBorderAndBackgroundWithFrame:NSOffsetRect(frame, -4, 0)
inView:controlView];
}
- (NSRect)rect {
NSSize size = [self cellSize];
return NSMakeRect(0, 0, std::ceil(size.width), std::ceil(size.height));
}
@end