1 // Copyright 2014 The Chromium Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
5 #import "chrome/browser/ui/cocoa/website_settings/split_block_button.h"
9 #include "base/logging.h"
10 #include "base/mac/scoped_nsobject.h"
11 #include "skia/ext/skia_utils_mac.h"
12 #import "ui/base/cocoa/menu_controller.h"
13 #include "ui/base/l10n/l10n_util_mac.h"
14 #include "ui/base/models/simple_menu_model.h"
29 NSBezierPath* PathWithCornerStyles(NSRect frame,
30 CornerType leftCornerStyle,
31 CornerType rightCornerStyle) {
32 base::scoped_nsobject<NSBezierPath> path([[NSBezierPath bezierPath] retain]);
33 const CGFloat x0 = NSMinX(frame);
34 const CGFloat x1 = NSMaxX(frame);
35 const CGFloat y0 = NSMinY(frame);
36 const CGFloat y1 = NSMaxY(frame);
37 const CGFloat radius = 2;
39 // Start at the center bottom. Draw left and up, including both left corners.
40 [path moveToPoint:NSMakePoint(std::floor((x1 - x0) * .5), y0)];
41 if (leftCornerStyle == kAngled) {
42 [path lineToPoint:NSMakePoint(x0, y0)];
43 [path lineToPoint:NSMakePoint(x0, y1)];
45 [path appendBezierPathWithArcFromPoint:NSMakePoint(x0, y0)
46 toPoint:NSMakePoint(x0, y0 + radius)
48 [path appendBezierPathWithArcFromPoint:NSMakePoint(x0, y1)
49 toPoint:NSMakePoint(x0 + radius, y1)
52 // Draw through the upper right-hand and lower-right-hand corners.
53 if (rightCornerStyle == kAngled) {
54 [path lineToPoint:NSMakePoint(x1, y1)];
55 [path lineToPoint:NSMakePoint(x1, y0)];
57 [path appendBezierPathWithArcFromPoint:NSMakePoint(x1, y1)
58 toPoint:NSMakePoint(x1, y1 - radius)
60 [path appendBezierPathWithArcFromPoint:NSMakePoint(x1, y0)
61 toPoint:NSMakePoint(x1 - radius, y0)
64 return path.autorelease();
67 void DrawBezel(id<ConstrainedWindowButtonDrawableCell>cell,
68 CornerType leftCorners,
69 CornerType rightCorners,
72 if ([cell isMouseInside]) {
73 base::scoped_nsobject<NSBezierPath> path(
74 [PathWithCornerStyles(frame, leftCorners, rightCorners) retain]);
75 [ConstrainedWindowButton DrawBackgroundAndShadowForPath:path
78 [ConstrainedWindowButton DrawInnerHighlightForPath:path
86 // A button cell used by SplitBlockButton, containing the title.
87 @interface SplitButtonTitleCell : ConstrainedWindowButtonCell
91 // A button cell used by SplitBlockButton, containing the popup menu.
92 @interface SplitButtonPopUpCell :
93 NSPopUpButtonCell<ConstrainedWindowButtonDrawableCell> {
96 base::scoped_nsobject<MenuController> menuController_;
97 scoped_ptr<ui::SimpleMenuModel> menuModel_;
100 // Designated initializer.
101 - (id)initWithMenuDelegate:(ui::SimpleMenuModel::Delegate*)menuDelegate;
107 @implementation SplitBlockButton
109 - (id)initWithMenuDelegate:(ui::SimpleMenuModel::Delegate*)menuDelegate {
110 if (self = [super initWithFrame:NSZeroRect]) {
111 leftCell_.reset([[SplitButtonTitleCell alloc] init]);
112 rightCell_.reset([[SplitButtonPopUpCell alloc]
113 initWithMenuDelegate:menuDelegate]);
114 [leftCell_ setTitle:l10n_util::GetNSString(IDS_PERMISSION_DENY)];
115 [leftCell_ setEnabled:YES];
116 [rightCell_ setEnabled:YES];
126 return [leftCell_ title];
129 - (void)setAction:(SEL)action {
130 [leftCell_ setAction:action];
133 - (void)setTarget:(id)target {
134 [leftCell_ setTarget:target];
137 - (void)drawRect:(NSRect)rect {
138 // Copy the base class: inset to leave room for the shadow.
141 // This function assumes that |rect| is always the same as [self frame].
142 // If that changes, the drawing functions will need to be adjusted.
143 const CGFloat radius = 2;
144 NSBezierPath* path = [NSBezierPath bezierPathWithRoundedRect:rect
147 [ConstrainedWindowButton DrawBackgroundAndShadowForPath:path
151 // Use intersection rects for the cell drawing, to ensure the height
152 // adjustment is honored.
153 [leftCell_ setControlView:self];
154 [leftCell_ drawWithFrame:NSIntersectionRect(rect, [self leftCellRect])
157 [rightCell_ setControlView:self];
158 [rightCell_ drawWithFrame:NSIntersectionRect(rect, [self rightCellRect])
162 path = [NSBezierPath bezierPathWithRoundedRect:NSInsetRect(rect, 0.5, 0.5)
165 [ConstrainedWindowButton DrawBorderForPath:path
170 - (void)updateTrackingAreas {
171 [self updateTrackingArea:&leftTrackingArea_
173 withRect:[self leftCellRect]];
175 [self updateTrackingArea:&rightTrackingArea_
177 withRect:[self rightCellRect]];
180 - (void)updateTrackingArea:(ui::ScopedCrTrackingArea*)trackingArea
181 forCell:(id<ConstrainedWindowButtonDrawableCell>)cell
182 withRect:(NSRect)rect {
183 DCHECK(trackingArea);
184 NSTrackingAreaOptions options = NSTrackingMouseEnteredAndExited |
185 NSTrackingActiveInActiveApp;
186 [self removeTrackingArea:trackingArea->get()];
187 trackingArea->reset([[CrTrackingArea alloc] initWithRect:rect
191 [self addTrackingArea:trackingArea->get()];
194 - (void)mouseEntered:(NSEvent*)theEvent {
195 [self mouseMoved:theEvent];
198 - (void)mouseExited:(NSEvent*)theEvent {
199 [self mouseMoved:theEvent];
202 - (void)mouseMoved:(NSEvent*)theEvent {
203 MouseLocation location = [self mouseLocationForEvent:theEvent];
204 [rightCell_ setIsMouseInside:NO];
205 [leftCell_ setIsMouseInside:NO];
206 if (location == kInsideLeftCell)
207 [leftCell_ setIsMouseInside:YES];
208 else if (location == kInsideRightCell)
209 [rightCell_ setIsMouseInside:YES];
210 [self setNeedsDisplay:YES];
213 - (void)mouseDown:(NSEvent*)theEvent {
214 MouseLocation downLocation = [self mouseLocationForEvent:theEvent];
215 NSCell* focusCell = nil;
217 if (downLocation == kInsideLeftCell) {
218 focusCell = leftCell_.get();
219 rect = [self leftCellRect];
220 } else if (downLocation == kInsideRightCell) {
221 focusCell = rightCell_.get();
222 rect = [self rightCellRect];
226 MouseLocation location = [self mouseLocationForEvent:theEvent];
227 if (location != kNotInside) {
228 [focusCell setHighlighted:YES];
229 [self setNeedsDisplay:YES];
231 if ([focusCell trackMouse:theEvent
235 [focusCell setState:![focusCell state]];
236 [self setNeedsDisplay:YES];
239 // The above -trackMouse call returned NO, so we know that
240 // the mouse left the cell before a mouse up event occurred.
241 [focusCell setHighlighted:NO];
242 [self setNeedsDisplay:YES];
245 const NSUInteger mask = NSLeftMouseUpMask | NSLeftMouseDraggedMask;
246 theEvent = [[self window] nextEventMatchingMask:mask];
247 } while ([theEvent type] != NSLeftMouseUp);
250 - (MouseLocation)mouseLocationForEvent:(NSEvent*)theEvent {
251 MouseLocation location = kNotInside;
252 NSPoint mousePoint = [self convertPoint:[theEvent locationInWindow]
254 if ([self mouse:mousePoint inRect:[leftCell_ rect]])
255 location = kInsideLeftCell;
256 else if ([self mouse:mousePoint inRect:[self rightCellRect]])
257 location = kInsideRightCell;
262 NSSize leftSize = [leftCell_ cellSize];
263 NSSize rightSize = [rightCell_ cellSize];
264 NSSize size = NSMakeSize(
265 std::max(leftSize.width + rightSize.width,
266 constrained_window_button::kButtonMinWidth),
267 std::max(leftSize.height, rightSize.height));
268 [self setFrameSize:size];
271 - (NSRect)leftCellRect {
272 return [leftCell_ rect];
275 - (NSRect)rightCellRect {
276 NSRect leftFrame, rightFrame;
277 NSDivideRect([self bounds], &leftFrame, &rightFrame,
278 NSWidth([self leftCellRect]), NSMinXEdge);
282 // Accessor for Testing.
284 return [rightCell_ menu];
289 @implementation SplitButtonTitleCell
291 - (void)drawBezelWithFrame:(NSRect)frame inView:(NSView *)controlView {
292 DrawBezel(self, kRounded, kAngled, frame, controlView);
296 NSSize size = [self cellSize];
297 return NSMakeRect(0, 0, std::ceil(size.width), std::ceil(size.height));
302 @implementation SplitButtonPopUpCell
304 @synthesize isMouseInside = isMouseInside_;
306 - (id)initWithMenuDelegate:(ui::SimpleMenuModel::Delegate*)menuDelegate {
307 if (self = [super initTextCell:@"" pullsDown:YES]) {
308 [self setControlSize:NSSmallControlSize];
309 [self setArrowPosition:NSPopUpArrowAtCenter];
310 [self setBordered:NO];
311 [self setBackgroundColor:[NSColor clearColor]];
312 menuModel_.reset(new ui::SimpleMenuModel(menuDelegate));
313 menuModel_->AddItemWithStringId(0, IDS_PERMISSION_CUSTOMIZE);
314 menuController_.reset(
315 [[MenuController alloc] initWithModel:menuModel_.get()
316 useWithPopUpButtonCell:NO]);
317 [self setMenu:[menuController_ menu]];
318 [self setUsesItemFromMenu:NO];
323 - (void)drawBorderAndBackgroundWithFrame:(NSRect)frame
324 inView:(NSView*)controlView {
325 // The arrow, which is what should be drawn by the base class, is drawn
326 // during -drawBezelWithFrame. The only way to draw our own border with
327 // the default arrow is to make the cell unbordered, and draw the border
328 // from -drawBorderAndBackgroundWithFrame, rather than simply overriding
329 // -drawBezelWithFrame.
330 DrawBezel(self, kAngled, kRounded, frame, controlView);
331 [super drawBorderAndBackgroundWithFrame:NSOffsetRect(frame, -4, 0)
336 NSSize size = [self cellSize];
337 return NSMakeRect(0, 0, std::ceil(size.width), std::ceil(size.height));