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 "chrome/grit/generated_resources.h"
12 #include "skia/ext/skia_utils_mac.h"
13 #import "ui/base/cocoa/menu_controller.h"
14 #include "ui/base/l10n/l10n_util_mac.h"
15 #include "ui/base/models/simple_menu_model.h"
30 NSBezierPath* PathWithCornerStyles(NSRect frame,
31 CornerType leftCornerStyle,
32 CornerType rightCornerStyle) {
33 base::scoped_nsobject<NSBezierPath> path([[NSBezierPath bezierPath] retain]);
34 const CGFloat x0 = NSMinX(frame);
35 const CGFloat x1 = NSMaxX(frame);
36 const CGFloat y0 = NSMinY(frame);
37 const CGFloat y1 = NSMaxY(frame);
38 const CGFloat radius = 2;
40 // Start at the center bottom. Draw left and up, including both left corners.
41 [path moveToPoint:NSMakePoint(std::floor((x1 - x0) * .5), y0)];
42 if (leftCornerStyle == kAngled) {
43 [path lineToPoint:NSMakePoint(x0, y0)];
44 [path lineToPoint:NSMakePoint(x0, y1)];
46 [path appendBezierPathWithArcFromPoint:NSMakePoint(x0, y0)
47 toPoint:NSMakePoint(x0, y0 + radius)
49 [path appendBezierPathWithArcFromPoint:NSMakePoint(x0, y1)
50 toPoint:NSMakePoint(x0 + radius, y1)
53 // Draw through the upper right-hand and lower-right-hand corners.
54 if (rightCornerStyle == kAngled) {
55 [path lineToPoint:NSMakePoint(x1, y1)];
56 [path lineToPoint:NSMakePoint(x1, y0)];
58 [path appendBezierPathWithArcFromPoint:NSMakePoint(x1, y1)
59 toPoint:NSMakePoint(x1, y1 - radius)
61 [path appendBezierPathWithArcFromPoint:NSMakePoint(x1, y0)
62 toPoint:NSMakePoint(x1 - radius, y0)
65 return path.autorelease();
68 void DrawBezel(id<ConstrainedWindowButtonDrawableCell>cell,
69 CornerType leftCorners,
70 CornerType rightCorners,
73 if ([cell isMouseInside]) {
74 base::scoped_nsobject<NSBezierPath> path(
75 [PathWithCornerStyles(frame, leftCorners, rightCorners) retain]);
76 [ConstrainedWindowButton DrawBackgroundAndShadowForPath:path
79 [ConstrainedWindowButton DrawInnerHighlightForPath:path
87 // A button cell used by SplitBlockButton, containing the title.
88 @interface SplitButtonTitleCell : ConstrainedWindowButtonCell
92 // A button cell used by SplitBlockButton, containing the popup menu.
93 @interface SplitButtonPopUpCell :
94 NSPopUpButtonCell<ConstrainedWindowButtonDrawableCell> {
97 base::scoped_nsobject<MenuController> menuController_;
98 scoped_ptr<ui::SimpleMenuModel> menuModel_;
101 // Designated initializer.
102 - (id)initWithMenuDelegate:(ui::SimpleMenuModel::Delegate*)menuDelegate;
108 @implementation SplitBlockButton
110 - (id)initWithMenuDelegate:(ui::SimpleMenuModel::Delegate*)menuDelegate {
111 if (self = [super initWithFrame:NSZeroRect]) {
112 leftCell_.reset([[SplitButtonTitleCell alloc] init]);
113 rightCell_.reset([[SplitButtonPopUpCell alloc]
114 initWithMenuDelegate:menuDelegate]);
115 [leftCell_ setTitle:l10n_util::GetNSString(IDS_PERMISSION_DENY)];
116 [leftCell_ setEnabled:YES];
117 [rightCell_ setEnabled:YES];
127 return [leftCell_ title];
130 - (void)setAction:(SEL)action {
131 [leftCell_ setAction:action];
134 - (void)setTarget:(id)target {
135 [leftCell_ setTarget:target];
138 - (void)drawRect:(NSRect)rect {
139 // Copy the base class: inset to leave room for the shadow.
142 // This function assumes that |rect| is always the same as [self frame].
143 // If that changes, the drawing functions will need to be adjusted.
144 const CGFloat radius = 2;
145 NSBezierPath* path = [NSBezierPath bezierPathWithRoundedRect:rect
148 [ConstrainedWindowButton DrawBackgroundAndShadowForPath:path
152 // Use intersection rects for the cell drawing, to ensure the height
153 // adjustment is honored.
154 [leftCell_ setControlView:self];
155 [leftCell_ drawWithFrame:NSIntersectionRect(rect, [self leftCellRect])
158 [rightCell_ setControlView:self];
159 [rightCell_ drawWithFrame:NSIntersectionRect(rect, [self rightCellRect])
163 path = [NSBezierPath bezierPathWithRoundedRect:NSInsetRect(rect, 0.5, 0.5)
166 [ConstrainedWindowButton DrawBorderForPath:path
171 - (void)updateTrackingAreas {
172 [self updateTrackingArea:&leftTrackingArea_
174 withRect:[self leftCellRect]];
176 [self updateTrackingArea:&rightTrackingArea_
178 withRect:[self rightCellRect]];
181 - (void)updateTrackingArea:(ui::ScopedCrTrackingArea*)trackingArea
182 forCell:(id<ConstrainedWindowButtonDrawableCell>)cell
183 withRect:(NSRect)rect {
184 DCHECK(trackingArea);
185 NSTrackingAreaOptions options = NSTrackingMouseEnteredAndExited |
186 NSTrackingActiveInActiveApp;
187 [self removeTrackingArea:trackingArea->get()];
188 trackingArea->reset([[CrTrackingArea alloc] initWithRect:rect
192 [self addTrackingArea:trackingArea->get()];
195 - (void)mouseEntered:(NSEvent*)theEvent {
196 [self mouseMoved:theEvent];
199 - (void)mouseExited:(NSEvent*)theEvent {
200 [self mouseMoved:theEvent];
203 - (void)mouseMoved:(NSEvent*)theEvent {
204 MouseLocation location = [self mouseLocationForEvent:theEvent];
205 [rightCell_ setIsMouseInside:NO];
206 [leftCell_ setIsMouseInside:NO];
207 if (location == kInsideLeftCell)
208 [leftCell_ setIsMouseInside:YES];
209 else if (location == kInsideRightCell)
210 [rightCell_ setIsMouseInside:YES];
211 [self setNeedsDisplay:YES];
214 - (void)mouseDown:(NSEvent*)theEvent {
215 MouseLocation downLocation = [self mouseLocationForEvent:theEvent];
216 NSCell* focusCell = nil;
218 if (downLocation == kInsideLeftCell) {
219 focusCell = leftCell_.get();
220 rect = [self leftCellRect];
221 } else if (downLocation == kInsideRightCell) {
222 focusCell = rightCell_.get();
223 rect = [self rightCellRect];
227 MouseLocation location = [self mouseLocationForEvent:theEvent];
228 if (location != kNotInside) {
229 [focusCell setHighlighted:YES];
230 [self setNeedsDisplay:YES];
232 if ([focusCell trackMouse:theEvent
236 [focusCell setState:![focusCell state]];
237 [self setNeedsDisplay:YES];
240 // The above -trackMouse call returned NO, so we know that
241 // the mouse left the cell before a mouse up event occurred.
242 [focusCell setHighlighted:NO];
243 [self setNeedsDisplay:YES];
246 const NSUInteger mask = NSLeftMouseUpMask | NSLeftMouseDraggedMask;
247 theEvent = [[self window] nextEventMatchingMask:mask];
248 } while ([theEvent type] != NSLeftMouseUp);
251 - (MouseLocation)mouseLocationForEvent:(NSEvent*)theEvent {
252 MouseLocation location = kNotInside;
253 NSPoint mousePoint = [self convertPoint:[theEvent locationInWindow]
255 if ([self mouse:mousePoint inRect:[leftCell_ rect]])
256 location = kInsideLeftCell;
257 else if ([self mouse:mousePoint inRect:[self rightCellRect]])
258 location = kInsideRightCell;
263 NSSize leftSize = [leftCell_ cellSize];
264 NSSize rightSize = [rightCell_ cellSize];
265 NSSize size = NSMakeSize(
266 std::ceil(std::max(leftSize.width + rightSize.width,
267 constrained_window_button::kButtonMinWidth)),
268 std::ceil(std::max(leftSize.height, rightSize.height)));
269 [self setFrameSize:size];
272 - (NSRect)leftCellRect {
273 return [leftCell_ rect];
276 - (NSRect)rightCellRect {
277 NSRect leftFrame, rightFrame;
278 NSDivideRect([self bounds], &leftFrame, &rightFrame,
279 NSWidth([self leftCellRect]), NSMinXEdge);
283 // Accessor for Testing.
285 return [rightCell_ menu];
290 @implementation SplitButtonTitleCell
292 - (void)drawBezelWithFrame:(NSRect)frame inView:(NSView *)controlView {
293 DrawBezel(self, kRounded, kAngled, frame, controlView);
297 NSSize size = [self cellSize];
298 return NSMakeRect(0, 0, std::ceil(size.width), std::ceil(size.height));
303 @implementation SplitButtonPopUpCell
305 @synthesize isMouseInside = isMouseInside_;
307 - (id)initWithMenuDelegate:(ui::SimpleMenuModel::Delegate*)menuDelegate {
308 if (self = [super initTextCell:@"" pullsDown:YES]) {
309 [self setControlSize:NSSmallControlSize];
310 [self setArrowPosition:NSPopUpArrowAtCenter];
311 [self setBordered:NO];
312 [self setBackgroundColor:[NSColor clearColor]];
313 menuModel_.reset(new ui::SimpleMenuModel(menuDelegate));
314 menuModel_->AddItemWithStringId(0, IDS_PERMISSION_CUSTOMIZE);
315 menuController_.reset(
316 [[MenuController alloc] initWithModel:menuModel_.get()
317 useWithPopUpButtonCell:NO]);
318 [self setMenu:[menuController_ menu]];
319 [self setUsesItemFromMenu:NO];
324 - (void)drawBorderAndBackgroundWithFrame:(NSRect)frame
325 inView:(NSView*)controlView {
326 // The arrow, which is what should be drawn by the base class, is drawn
327 // during -drawBezelWithFrame. The only way to draw our own border with
328 // the default arrow is to make the cell unbordered, and draw the border
329 // from -drawBorderAndBackgroundWithFrame, rather than simply overriding
330 // -drawBezelWithFrame.
331 DrawBezel(self, kAngled, kRounded, frame, controlView);
332 [super drawBorderAndBackgroundWithFrame:NSOffsetRect(frame, -4, 0)
337 NSSize size = [self cellSize];
338 return NSMakeRect(0, 0, std::ceil(size.width), std::ceil(size.height));