| OLD | NEW |
| (Empty) |
| 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 "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" | |
| 16 | |
| 17 namespace { | |
| 18 | |
| 19 enum MouseLocation { | |
| 20 kInsideLeftCell, | |
| 21 kInsideRightCell, | |
| 22 kNotInside, | |
| 23 }; | |
| 24 | |
| 25 enum CornerType { | |
| 26 kRounded, | |
| 27 kAngled, | |
| 28 }; | |
| 29 | |
| 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; | |
| 39 | |
| 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(); | |
| 66 } | |
| 67 | |
| 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 } | |
| 83 } | |
| 84 | |
| 85 } // namespace | |
| 86 | |
| 87 // A button cell used by SplitBlockButton, containing the title. | |
| 88 @interface SplitButtonTitleCell : ConstrainedWindowButtonCell | |
| 89 - (NSRect)rect; | |
| 90 @end | |
| 91 | |
| 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 std::unique_ptr<ui::SimpleMenuModel> menuModel_; | |
| 99 } | |
| 100 | |
| 101 // Designated initializer. | |
| 102 - (id)initWithMenuDelegate:(ui::SimpleMenuModel::Delegate*)menuDelegate; | |
| 103 | |
| 104 - (NSRect)rect; | |
| 105 | |
| 106 @end | |
| 107 | |
| 108 @implementation SplitBlockButton | |
| 109 | |
| 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; | |
| 120 } | |
| 121 | |
| 122 + (Class)cellClass { | |
| 123 return nil; | |
| 124 } | |
| 125 | |
| 126 - (NSString*)title { | |
| 127 return [leftCell_ title]; | |
| 128 } | |
| 129 | |
| 130 - (void)setAction:(SEL)action { | |
| 131 [leftCell_ setAction:action]; | |
| 132 } | |
| 133 | |
| 134 - (void)setTarget:(id)target { | |
| 135 [leftCell_ setTarget:target]; | |
| 136 } | |
| 137 | |
| 138 - (void)drawRect:(NSRect)rect { | |
| 139 // Copy the base class: inset to leave room for the shadow. | |
| 140 --rect.size.height; | |
| 141 | |
| 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]; | |
| 151 | |
| 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]; | |
| 157 | |
| 158 [rightCell_ setControlView:self]; | |
| 159 [rightCell_ drawWithFrame:NSIntersectionRect(rect, [self rightCellRect]) | |
| 160 inView:self]; | |
| 161 | |
| 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]; | |
| 169 } | |
| 170 | |
| 171 - (void)updateTrackingAreas { | |
| 172 [self updateTrackingArea:&leftTrackingArea_ | |
| 173 forCell:leftCell_ | |
| 174 withRect:[self leftCellRect]]; | |
| 175 | |
| 176 [self updateTrackingArea:&rightTrackingArea_ | |
| 177 forCell:rightCell_ | |
| 178 withRect:[self rightCellRect]]; | |
| 179 } | |
| 180 | |
| 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()]; | |
| 193 } | |
| 194 | |
| 195 - (void)mouseEntered:(NSEvent*)theEvent { | |
| 196 [self mouseMoved:theEvent]; | |
| 197 } | |
| 198 | |
| 199 - (void)mouseExited:(NSEvent*)theEvent { | |
| 200 [self mouseMoved:theEvent]; | |
| 201 } | |
| 202 | |
| 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]; | |
| 212 } | |
| 213 | |
| 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 } | |
| 225 | |
| 226 do { | |
| 227 MouseLocation location = [self mouseLocationForEvent:theEvent]; | |
| 228 if (location != kNotInside) { | |
| 229 [focusCell setHighlighted:YES]; | |
| 230 [self setNeedsDisplay:YES]; | |
| 231 | |
| 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); | |
| 249 } | |
| 250 | |
| 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; | |
| 260 } | |
| 261 | |
| 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]; | |
| 270 } | |
| 271 | |
| 272 - (NSRect)leftCellRect { | |
| 273 return [leftCell_ rect]; | |
| 274 } | |
| 275 | |
| 276 - (NSRect)rightCellRect { | |
| 277 NSRect leftFrame, rightFrame; | |
| 278 NSDivideRect([self bounds], &leftFrame, &rightFrame, | |
| 279 NSWidth([self leftCellRect]), NSMinXEdge); | |
| 280 return rightFrame; | |
| 281 } | |
| 282 | |
| 283 // Accessor for Testing. | |
| 284 - (NSMenu*)menu { | |
| 285 return [rightCell_ menu]; | |
| 286 } | |
| 287 | |
| 288 @end | |
| 289 | |
| 290 @implementation SplitButtonTitleCell | |
| 291 | |
| 292 - (void)drawBezelWithFrame:(NSRect)frame inView:(NSView *)controlView { | |
| 293 DrawBezel(self, kRounded, kAngled, frame, controlView); | |
| 294 } | |
| 295 | |
| 296 - (NSRect)rect { | |
| 297 NSSize size = [self cellSize]; | |
| 298 return NSMakeRect(0, 0, std::ceil(size.width), std::ceil(size.height)); | |
| 299 } | |
| 300 | |
| 301 @end | |
| 302 | |
| 303 @implementation SplitButtonPopUpCell | |
| 304 | |
| 305 @synthesize isMouseInside = isMouseInside_; | |
| 306 | |
| 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; | |
| 322 } | |
| 323 | |
| 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]; | |
| 334 } | |
| 335 | |
| 336 - (NSRect)rect { | |
| 337 NSSize size = [self cellSize]; | |
| 338 return NSMakeRect(0, 0, std::ceil(size.width), std::ceil(size.height)); | |
| 339 } | |
| 340 | |
| 341 @end | |
| OLD | NEW |