Upstream version 7.36.149.0
[platform/framework/web/crosswalk.git] / src / chrome / browser / ui / cocoa / website_settings / split_block_button.mm
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.
4
5 #import "chrome/browser/ui/cocoa/website_settings/split_block_button.h"
6
7 #include <cmath>
8
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"
15
16 namespace {
17
18 enum MouseLocation {
19   kInsideLeftCell,
20   kInsideRightCell,
21   kNotInside,
22 };
23
24 enum CornerType {
25   kRounded,
26   kAngled,
27 };
28
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;
38
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)];
44   } else {
45     [path appendBezierPathWithArcFromPoint:NSMakePoint(x0, y0)
46                                    toPoint:NSMakePoint(x0, y0 + radius)
47                                     radius:radius];
48     [path appendBezierPathWithArcFromPoint:NSMakePoint(x0, y1)
49                                    toPoint:NSMakePoint(x0 + radius, y1)
50                                     radius:radius];
51   }
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)];
56   } else {
57     [path appendBezierPathWithArcFromPoint:NSMakePoint(x1, y1)
58                                    toPoint:NSMakePoint(x1, y1 - radius)
59                                     radius:radius];
60     [path appendBezierPathWithArcFromPoint:NSMakePoint(x1, y0)
61                                    toPoint:NSMakePoint(x1 - radius, y0)
62                                     radius:radius];
63   }
64   return path.autorelease();
65 }
66
67 void DrawBezel(id<ConstrainedWindowButtonDrawableCell>cell,
68                CornerType leftCorners,
69                CornerType rightCorners,
70                NSRect frame,
71                NSView* view) {
72   if ([cell isMouseInside]) {
73     base::scoped_nsobject<NSBezierPath> path(
74         [PathWithCornerStyles(frame, leftCorners, rightCorners) retain]);
75     [ConstrainedWindowButton DrawBackgroundAndShadowForPath:path
76                                                    withCell:cell
77                                                      inView:view];
78     [ConstrainedWindowButton DrawInnerHighlightForPath:path
79                                               withCell:cell
80                                                 inView:view];
81   }
82 }
83
84 }  // namespace
85
86 // A button cell used by SplitBlockButton, containing the title.
87 @interface SplitButtonTitleCell : ConstrainedWindowButtonCell
88 - (NSRect)rect;
89 @end
90
91 // A button cell used by SplitBlockButton, containing the popup menu.
92 @interface SplitButtonPopUpCell :
93     NSPopUpButtonCell<ConstrainedWindowButtonDrawableCell> {
94  @private
95   BOOL isMouseInside_;
96   base::scoped_nsobject<MenuController> menuController_;
97   scoped_ptr<ui::SimpleMenuModel> menuModel_;
98 }
99
100 // Designated initializer.
101 - (id)initWithMenuDelegate:(ui::SimpleMenuModel::Delegate*)menuDelegate;
102
103 - (NSRect)rect;
104
105 @end
106
107 @implementation SplitBlockButton
108
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];
117   }
118   return self;
119 }
120
121 + (Class)cellClass {
122   return nil;
123 }
124
125 - (NSString*)title {
126   return [leftCell_ title];
127 }
128
129 - (void)setAction:(SEL)action {
130   [leftCell_ setAction:action];
131 }
132
133 - (void)setTarget:(id)target {
134   [leftCell_ setTarget:target];
135 }
136
137 - (void)drawRect:(NSRect)rect {
138   // Copy the base class:  inset to leave room for the shadow.
139   --rect.size.height;
140
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
145                                                        xRadius:radius
146                                                        yRadius:radius];
147   [ConstrainedWindowButton DrawBackgroundAndShadowForPath:path
148                                                  withCell:nil
149                                                    inView:self];
150
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])
155                     inView:self];
156
157   [rightCell_ setControlView:self];
158   [rightCell_ drawWithFrame:NSIntersectionRect(rect, [self rightCellRect])
159                      inView:self];
160
161   // Draw the border.
162   path = [NSBezierPath bezierPathWithRoundedRect:NSInsetRect(rect, 0.5, 0.5)
163                                          xRadius:radius
164                                          yRadius:radius];
165   [ConstrainedWindowButton DrawBorderForPath:path
166                                     withCell:nil
167                                       inView:self];
168 }
169
170 - (void)updateTrackingAreas {
171   [self updateTrackingArea:&leftTrackingArea_
172                    forCell:leftCell_
173                   withRect:[self leftCellRect]];
174
175   [self updateTrackingArea:&rightTrackingArea_
176                    forCell:rightCell_
177                   withRect:[self rightCellRect]];
178 }
179
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
188                                                    options:options
189                                                      owner:self
190                                                   userInfo:nil]);
191   [self addTrackingArea:trackingArea->get()];
192 }
193
194 - (void)mouseEntered:(NSEvent*)theEvent {
195   [self mouseMoved:theEvent];
196 }
197
198 - (void)mouseExited:(NSEvent*)theEvent {
199   [self mouseMoved:theEvent];
200 }
201
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];
211 }
212
213 - (void)mouseDown:(NSEvent*)theEvent {
214   MouseLocation downLocation = [self mouseLocationForEvent:theEvent];
215   NSCell* focusCell = nil;
216   NSRect rect;
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];
223   }
224
225   do {
226     MouseLocation location = [self mouseLocationForEvent:theEvent];
227     if (location != kNotInside) {
228       [focusCell setHighlighted:YES];
229       [self setNeedsDisplay:YES];
230
231       if ([focusCell trackMouse:theEvent
232                          inRect:rect
233                          ofView:self
234                    untilMouseUp:NO]) {
235         [focusCell setState:![focusCell state]];
236         [self setNeedsDisplay:YES];
237         break;
238       } else {
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];
243       }
244     }
245     const NSUInteger mask = NSLeftMouseUpMask | NSLeftMouseDraggedMask;
246     theEvent = [[self window] nextEventMatchingMask:mask];
247   } while ([theEvent type] != NSLeftMouseUp);
248 }
249
250 - (MouseLocation)mouseLocationForEvent:(NSEvent*)theEvent {
251   MouseLocation location = kNotInside;
252   NSPoint mousePoint = [self convertPoint:[theEvent locationInWindow]
253                                  fromView:nil];
254   if ([self mouse:mousePoint inRect:[leftCell_ rect]])
255     location = kInsideLeftCell;
256   else if ([self mouse:mousePoint inRect:[self rightCellRect]])
257     location = kInsideRightCell;
258   return location;
259 }
260
261 - (void)sizeToFit {
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];
269 }
270
271 - (NSRect)leftCellRect {
272   return [leftCell_ rect];
273 }
274
275 - (NSRect)rightCellRect {
276   NSRect leftFrame, rightFrame;
277   NSDivideRect([self bounds], &leftFrame, &rightFrame,
278                NSWidth([self leftCellRect]), NSMinXEdge);
279   return rightFrame;
280 }
281
282 // Accessor for Testing.
283 - (NSMenu*)menu {
284   return [rightCell_ menu];
285 }
286
287 @end
288
289 @implementation SplitButtonTitleCell
290
291 - (void)drawBezelWithFrame:(NSRect)frame inView:(NSView *)controlView {
292   DrawBezel(self, kRounded, kAngled, frame, controlView);
293 }
294
295 - (NSRect)rect {
296   NSSize size = [self cellSize];
297   return NSMakeRect(0, 0, std::ceil(size.width), std::ceil(size.height));
298 }
299
300 @end
301
302 @implementation SplitButtonPopUpCell
303
304 @synthesize isMouseInside = isMouseInside_;
305
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];
319   }
320   return self;
321 }
322
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)
332                                    inView:controlView];
333 }
334
335 - (NSRect)rect {
336   NSSize size = [self cellSize];
337   return NSMakeRect(0, 0, std::ceil(size.width), std::ceil(size.height));
338 }
339
340 @end