Merge Chromium + Blink git repositories
[chromium-blink-merge.git] / chrome / browser / ui / cocoa / website_settings / split_block_button.mm
blobadb6b6034c2328c170fe7f28eaf8a8cb6c77c734
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"
7 #include <cmath>
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"
17 namespace {
19 enum MouseLocation {
20   kInsideLeftCell,
21   kInsideRightCell,
22   kNotInside,
25 enum CornerType {
26   kRounded,
27   kAngled,
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)];
45   } else {
46     [path appendBezierPathWithArcFromPoint:NSMakePoint(x0, y0)
47                                    toPoint:NSMakePoint(x0, y0 + radius)
48                                     radius:radius];
49     [path appendBezierPathWithArcFromPoint:NSMakePoint(x0, y1)
50                                    toPoint:NSMakePoint(x0 + radius, y1)
51                                     radius:radius];
52   }
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)];
57   } else {
58     [path appendBezierPathWithArcFromPoint:NSMakePoint(x1, y1)
59                                    toPoint:NSMakePoint(x1, y1 - radius)
60                                     radius:radius];
61     [path appendBezierPathWithArcFromPoint:NSMakePoint(x1, y0)
62                                    toPoint:NSMakePoint(x1 - radius, y0)
63                                     radius:radius];
64   }
65   return path.autorelease();
68 void DrawBezel(id<ConstrainedWindowButtonDrawableCell>cell,
69                CornerType leftCorners,
70                CornerType rightCorners,
71                NSRect frame,
72                NSView* view) {
73   if ([cell isMouseInside]) {
74     base::scoped_nsobject<NSBezierPath> path(
75         [PathWithCornerStyles(frame, leftCorners, rightCorners) retain]);
76     [ConstrainedWindowButton DrawBackgroundAndShadowForPath:path
77                                                    withCell:cell
78                                                      inView:view];
79     [ConstrainedWindowButton DrawInnerHighlightForPath:path
80                                               withCell:cell
81                                                 inView:view];
82   }
85 }  // namespace
87 // A button cell used by SplitBlockButton, containing the title.
88 @interface SplitButtonTitleCell : ConstrainedWindowButtonCell
89 - (NSRect)rect;
90 @end
92 // A button cell used by SplitBlockButton, containing the popup menu.
93 @interface SplitButtonPopUpCell :
94     NSPopUpButtonCell<ConstrainedWindowButtonDrawableCell> {
95  @private
96   BOOL isMouseInside_;
97   base::scoped_nsobject<MenuController> menuController_;
98   scoped_ptr<ui::SimpleMenuModel> menuModel_;
101 // Designated initializer.
102 - (id)initWithMenuDelegate:(ui::SimpleMenuModel::Delegate*)menuDelegate;
104 - (NSRect)rect;
106 @end
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];
118   }
119   return self;
122 + (Class)cellClass {
123   return nil;
126 - (NSString*)title {
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.
140   --rect.size.height;
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
146                                                        xRadius:radius
147                                                        yRadius:radius];
148   [ConstrainedWindowButton DrawBackgroundAndShadowForPath:path
149                                                  withCell:nil
150                                                    inView:self];
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])
156                     inView:self];
158   [rightCell_ setControlView:self];
159   [rightCell_ drawWithFrame:NSIntersectionRect(rect, [self rightCellRect])
160                      inView:self];
162   // Draw the border.
163   path = [NSBezierPath bezierPathWithRoundedRect:NSInsetRect(rect, 0.5, 0.5)
164                                          xRadius:radius
165                                          yRadius:radius];
166   [ConstrainedWindowButton DrawBorderForPath:path
167                                     withCell:nil
168                                       inView:self];
171 - (void)updateTrackingAreas {
172   [self updateTrackingArea:&leftTrackingArea_
173                    forCell:leftCell_
174                   withRect:[self leftCellRect]];
176   [self updateTrackingArea:&rightTrackingArea_
177                    forCell:rightCell_
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
189                                                    options:options
190                                                      owner:self
191                                                   userInfo:nil]);
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;
217   NSRect rect;
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];
224   }
226   do {
227     MouseLocation location = [self mouseLocationForEvent:theEvent];
228     if (location != kNotInside) {
229       [focusCell setHighlighted:YES];
230       [self setNeedsDisplay:YES];
232       if ([focusCell trackMouse:theEvent
233                          inRect:rect
234                          ofView:self
235                    untilMouseUp:NO]) {
236         [focusCell setState:![focusCell state]];
237         [self setNeedsDisplay:YES];
238         break;
239       } else {
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];
244       }
245     }
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]
254                                  fromView:nil];
255   if ([self mouse:mousePoint inRect:[leftCell_ rect]])
256     location = kInsideLeftCell;
257   else if ([self mouse:mousePoint inRect:[self rightCellRect]])
258     location = kInsideRightCell;
259   return location;
262 - (void)sizeToFit {
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);
280   return rightFrame;
283 // Accessor for Testing.
284 - (NSMenu*)menu {
285   return [rightCell_ menu];
288 @end
290 @implementation SplitButtonTitleCell
292 - (void)drawBezelWithFrame:(NSRect)frame inView:(NSView *)controlView {
293   DrawBezel(self, kRounded, kAngled, frame, controlView);
296 - (NSRect)rect {
297   NSSize size = [self cellSize];
298   return NSMakeRect(0, 0, std::ceil(size.width), std::ceil(size.height));
301 @end
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];
320   }
321   return self;
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)
333                                    inView:controlView];
336 - (NSRect)rect {
337   NSSize size = [self cellSize];
338   return NSMakeRect(0, 0, std::ceil(size.width), std::ceil(size.height));
341 @end