Chromium Code Reviews| OLD | NEW |
|---|---|
| (Empty) | |
| 1 // Copyright (c) 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/permission_bubble_controller.h" | |
| 6 | |
| 7 #include <algorithm> | |
| 8 | |
| 9 #include "base/mac/foundation_util.h" | |
| 10 #include "base/mac/mac_util.h" | |
| 11 #include "base/strings/sys_string_conversions.h" | |
| 12 #include "chrome/browser/ui/browser.h" | |
| 13 #include "chrome/browser/ui/browser_finder.h" | |
| 14 #import "chrome/browser/ui/chrome_style.h" | |
| 15 #import "chrome/browser/ui/cocoa/browser_window_controller.h" | |
| 16 #import "chrome/browser/ui/cocoa/hyperlink_text_view.h" | |
| 17 #import "chrome/browser/ui/cocoa/info_bubble_view.h" | |
| 18 #import "chrome/browser/ui/cocoa/info_bubble_window.h" | |
| 19 #include "chrome/browser/ui/cocoa/permission_bubble_cocoa.h" | |
| 20 #include "chrome/browser/ui/website_settings/permission_bubble_delegate.h" | |
| 21 #include "chrome/browser/ui/website_settings/permission_bubble_view.h" | |
| 22 #include "content/public/browser/user_metrics.h" | |
| 23 #include "grit/generated_resources.h" | |
| 24 #include "skia/ext/skia_utils_mac.h" | |
| 25 #include "ui/base/l10n/l10n_util_mac.h" | |
| 26 | |
| 27 using base::UserMetricsAction; | |
| 28 | |
| 29 namespace { | |
| 30 const CGFloat kHorizontalPadding = 20.0f; | |
| 31 const CGFloat kVerticalPadding = 20.0f; | |
| 32 const CGFloat kButtonPadding = 10.0f; | |
| 33 const CGFloat kCheckboxYAdjustment = 2.0f; | |
| 34 | |
| 35 const CGFloat kButtonWidth = 80.0f; | |
| 36 const CGFloat kButtonHeight = 30.0f; | |
| 37 | |
| 38 const CGFloat kFontSize = 15.0f; | |
| 39 const base::char16 kBulletPoint = 0x2022; | |
| 40 const int kButtonBackgroundColor = 239; | |
| 41 | |
| 42 } // namespace | |
| 43 | |
| 44 @interface PermissionBubbleController () | |
| 45 | |
| 46 // Called when the 'ok' button is pressed. | |
| 47 - (void)ok:(id)sender; | |
| 48 | |
| 49 // Called when the 'allow' button is pressed. | |
| 50 - (void)onAllow:(id)sender; | |
| 51 | |
| 52 // Called when the 'block' button is pressed. | |
| 53 - (void)onBlock:(id)sender; | |
| 54 | |
| 55 // Called when the 'customize' button is pressed. | |
| 56 - (void)onCustomize:(id)sender; | |
| 57 | |
| 58 // Called when a checkbox changes from checked to unchecked, or vice versa. | |
| 59 - (void)onCheckboxChanged:(id)sender; | |
| 60 | |
| 61 @end | |
| 62 | |
| 63 @implementation PermissionBubbleController | |
| 64 | |
| 65 - (id)initWithParentWindow:(NSWindow*)parentWindow | |
| 66 bridge:(PermissionBubbleCocoa*)bridge { | |
| 67 DCHECK(parentWindow); | |
| 68 DCHECK(bridge); | |
| 69 base::scoped_nsobject<InfoBubbleWindow> window( | |
| 70 [[InfoBubbleWindow alloc] initWithContentRect:NSMakeRect(0, 0, 240, 150) | |
| 71 styleMask:NSBorderlessWindowMask | |
| 72 backing:NSBackingStoreBuffered | |
| 73 defer:NO]); | |
| 74 [window setAllowedAnimations:info_bubble::kAnimateNone]; | |
| 75 if ((self = [super initWithWindow:window | |
| 76 parentWindow:parentWindow | |
| 77 anchoredAt:NSZeroPoint])) { | |
| 78 [self setShouldCloseOnResignKey:NO]; | |
| 79 [[self bubble] setArrowLocation:info_bubble::kTopLeft]; | |
| 80 bridge_ = bridge; | |
| 81 } | |
| 82 return self; | |
| 83 } | |
| 84 | |
| 85 - (void)windowWillClose:(NSNotification*)notification { | |
| 86 bridge_->OnBubbleClosing(); | |
| 87 [super windowWillClose:notification]; | |
| 88 } | |
| 89 | |
| 90 - (void)showAtAnchor:(NSPoint)anchorPoint | |
| 91 withDelegate:(PermissionBubbleView::Delegate*)delegate | |
| 92 forRequests:(const std::vector<PermissionBubbleDelegate*>&)requests | |
| 93 acceptStates:(const std::vector<bool>&)acceptStates | |
| 94 customizationMode:(BOOL)customizationMode { | |
| 95 DCHECK(delegate); | |
| 96 DCHECK(!customizationMode || (requests.size() == acceptStates.size())); | |
| 97 delegate_ = delegate; | |
| 98 | |
| 99 NSView* contentView = [[self window] contentView]; | |
| 100 DCHECK([[contentView subviews] count] == 0); | |
| 101 | |
| 102 BOOL singlePermission = requests.size() == 1; | |
| 103 NSRect bubbleFrame = [[self window] frame]; | |
| 104 CGFloat yOffset = 2 * kVerticalPadding + kButtonHeight; | |
| 105 | |
| 106 checkboxes_.reset(customizationMode ? [[NSMutableArray alloc] init] : nil); | |
| 107 for (auto it = requests.begin(); it != requests.end(); it++) { | |
|
groby-ooo-7-16
2014/02/04 02:39:30
I envy our Cocoa code the ability to use auto... W
| |
| 108 base::scoped_nsobject<NSView> permissionView; | |
| 109 if (customizationMode) { | |
| 110 int index = it - requests.begin(); | |
| 111 permissionView.reset( | |
| 112 [[self checkboxForRequest:(*it) | |
| 113 checked:acceptStates[index] ? YES : NO] retain]); | |
| 114 [checkboxes_ addObject:permissionView]; | |
| 115 } else { | |
| 116 permissionView.reset([[self labelForRequest:(*it) | |
| 117 isSingleRequest:singlePermission] retain]); | |
| 118 } | |
| 119 NSPoint origin = [permissionView frame].origin; | |
| 120 origin.x += kHorizontalPadding; | |
| 121 origin.y += yOffset; | |
| 122 [permissionView setFrameOrigin:origin]; | |
| 123 [contentView addSubview:permissionView]; | |
| 124 | |
| 125 yOffset += NSHeight([permissionView frame]); | |
| 126 } | |
| 127 | |
| 128 if (!singlePermission && !customizationMode) { | |
| 129 base::scoped_nsobject<NSView> customizeButton( | |
| 130 [[self customizationButton] retain]); | |
| 131 // The Y center should match the Y centers of the buttons. | |
| 132 CGFloat customizeButtonYOffset = kVerticalPadding + | |
| 133 (kButtonHeight - std::ceil(NSHeight([customizeButton frame])) * 0.5f); | |
| 134 [customizeButton setFrameOrigin:NSMakePoint(kHorizontalPadding, | |
| 135 customizeButtonYOffset)]; | |
| 136 [contentView addSubview:customizeButton]; | |
| 137 } | |
| 138 | |
| 139 // The maximum width of the above permissions will dictate the width of the | |
| 140 // bubble. It is calculated here so that it can be used for the positioning | |
| 141 // of the buttons. | |
| 142 for (NSView* view in [contentView subviews]) | |
| 143 bubbleFrame = NSUnionRect(bubbleFrame, [view frame]); | |
| 144 bubbleFrame.size.width += 250; | |
|
groby-ooo-7-16
2014/02/04 02:39:30
Please call out as constant - I'm not sure why 250
leng
2014/02/04 21:48:22
That was a testing leftover... thanks for catchin
| |
| 145 | |
| 146 base::scoped_nsobject<NSView> allowOrOkButton; | |
| 147 if (customizationMode) { | |
| 148 allowOrOkButton.reset([[self buttonWithTitle:@"OK" | |
| 149 action:@selector(ok:)] retain]); | |
| 150 } else { | |
| 151 allowOrOkButton.reset([[self buttonWithTitle:@"Allow" | |
| 152 action:@selector(onAllow:)] retain]); | |
| 153 } | |
| 154 CGFloat xOrigin = NSWidth(bubbleFrame) - kButtonWidth - kHorizontalPadding; | |
| 155 [allowOrOkButton setFrameOrigin:NSMakePoint(xOrigin, kVerticalPadding)]; | |
| 156 [contentView addSubview:allowOrOkButton]; | |
| 157 | |
| 158 if (!customizationMode) { | |
| 159 base::scoped_nsobject<NSView> blockButton( | |
| 160 [[self buttonWithTitle:@"Block" action:@selector(onBlock:)] retain]); | |
| 161 xOrigin = NSMinX([allowOrOkButton frame]) - NSWidth([blockButton frame]) - | |
| 162 kButtonPadding; | |
| 163 [blockButton setFrameOrigin:NSMakePoint(xOrigin, kVerticalPadding)]; | |
| 164 [contentView addSubview:blockButton]; | |
| 165 } | |
| 166 | |
| 167 if (!singlePermission) { | |
| 168 base::scoped_nsobject<NSView> titleView( | |
| 169 [[self titleForMultipleRequests] retain]); | |
| 170 [contentView addSubview:titleView]; | |
| 171 [titleView setFrameOrigin:NSMakePoint(kHorizontalPadding, | |
| 172 kVerticalPadding + yOffset)]; | |
| 173 yOffset += NSHeight([titleView frame]) + kVerticalPadding; | |
| 174 } | |
| 175 | |
| 176 bubbleFrame.size.height = yOffset + kVerticalPadding; | |
| 177 bubbleFrame = [[self window] frameRectForContentRect:bubbleFrame]; | |
| 178 [[self window] setFrame:bubbleFrame display:NO]; | |
| 179 | |
| 180 [self setAnchorPoint:anchorPoint]; | |
| 181 [self showWindow:nil]; | |
| 182 } | |
| 183 | |
| 184 - (NSView*)labelForRequest:(PermissionBubbleDelegate*)request | |
| 185 isSingleRequest:(BOOL)singleRequest { | |
| 186 DCHECK(request); | |
| 187 base::scoped_nsobject<NSTextField> permissionLabel( | |
| 188 [[NSTextField alloc] initWithFrame:NSZeroRect]); | |
| 189 base::string16 label; | |
| 190 if (!singleRequest) { | |
| 191 label.push_back(kBulletPoint); | |
| 192 label.push_back(' '); | |
| 193 } | |
| 194 if (singleRequest) { | |
| 195 // TODO(leng): Make the appropriate call when it's working. It should call | |
| 196 // GetMessageText(), but it's not returning the correct string yet. | |
| 197 label += request->GetMessageTextFragment(); | |
| 198 } else { | |
| 199 label += request->GetMessageTextFragment(); | |
| 200 } | |
| 201 [permissionLabel setDrawsBackground:NO]; | |
| 202 [permissionLabel setBezeled:NO]; | |
| 203 [permissionLabel setEditable:NO]; | |
| 204 [permissionLabel setSelectable:NO]; | |
| 205 [permissionLabel setStringValue:base::SysUTF16ToNSString(label)]; | |
| 206 [permissionLabel sizeToFit]; | |
| 207 return permissionLabel.autorelease(); | |
| 208 } | |
| 209 | |
| 210 - (NSView*)titleForMultipleRequests { | |
| 211 base::scoped_nsobject<NSTextField> titleView( | |
| 212 [[NSTextField alloc] initWithFrame:NSZeroRect]); | |
| 213 [titleView setDrawsBackground:NO]; | |
| 214 [titleView setBezeled:NO]; | |
| 215 [titleView setEditable:NO]; | |
| 216 [titleView setSelectable:NO]; | |
| 217 [titleView setStringValue:@"This site would like to:"]; | |
| 218 [titleView setFont:[NSFont systemFontOfSize:kFontSize]]; | |
| 219 [titleView sizeToFit]; | |
| 220 return titleView.autorelease(); | |
| 221 } | |
| 222 | |
| 223 - (NSView*)checkboxForRequest:(PermissionBubbleDelegate*)request | |
| 224 checked:(BOOL)checked { | |
| 225 DCHECK(request); | |
| 226 base::scoped_nsobject<NSButton> checkbox( | |
| 227 [[NSButton alloc] initWithFrame:NSZeroRect]); | |
| 228 [checkbox setButtonType:NSSwitchButton]; | |
| 229 base::string16 permission = request->GetMessageTextFragment(); | |
| 230 [checkbox setTitle:base::SysUTF16ToNSString(permission)]; | |
| 231 [checkbox setState:(checked ? NSOnState : NSOffState)]; | |
| 232 [checkbox setTarget:self]; | |
| 233 [checkbox setAction:@selector(onCheckboxChanged:)]; | |
| 234 [checkbox sizeToFit]; | |
| 235 [checkbox setFrameOrigin:NSMakePoint(0, kCheckboxYAdjustment)]; | |
| 236 return checkbox.autorelease(); | |
| 237 } | |
| 238 | |
| 239 - (NSView*)customizationButton { | |
| 240 NSColor* linkColor = | |
| 241 gfx::SkColorToCalibratedNSColor(chrome_style::GetLinkColor()); | |
| 242 base::scoped_nsobject<NSButton> customizeButton( | |
| 243 [[NSButton alloc] initWithFrame:NSZeroRect]); | |
| 244 [customizeButton setButtonType:NSMomentaryChangeButton]; | |
| 245 [customizeButton setAttributedTitle:[[NSAttributedString alloc] | |
| 246 initWithString:@"Customize" | |
| 247 attributes:@{ NSForegroundColorAttributeName : linkColor }]]; | |
| 248 [customizeButton setTarget:self]; | |
| 249 [customizeButton setAction:@selector(onCustomize:)]; | |
| 250 [customizeButton sizeToFit]; | |
| 251 return customizeButton.autorelease(); | |
| 252 } | |
| 253 | |
| 254 - (NSView*)buttonWithTitle:(NSString*)title | |
| 255 action:(SEL)action { | |
| 256 NSRect frame = NSMakeRect(0, 0, kButtonWidth, kButtonHeight); | |
| 257 NSColor* buttonBackgroundColor = | |
| 258 gfx::SkColorToCalibratedNSColor(SkColorSetRGB(kButtonBackgroundColor, | |
| 259 kButtonBackgroundColor, | |
| 260 kButtonBackgroundColor)); | |
| 261 | |
| 262 base::scoped_nsobject<NSButton> button( | |
| 263 [[NSButton alloc] initWithFrame:frame]); | |
| 264 [button setButtonType:NSMomentaryPushInButton]; | |
| 265 [button setTitle:title]; | |
| 266 [button setTarget:self]; | |
| 267 [button setAction:action]; | |
| 268 [button setBordered:NO]; | |
| 269 [[button cell] setBackgroundColor:buttonBackgroundColor]; | |
|
groby-ooo-7-16
2014/02/04 02:39:30
I'm wondering about the backgroundcolor thing. Loo
leng
2014/02/04 21:48:22
I did - and we're using the version with the borde
| |
| 270 return button.autorelease(); | |
| 271 } | |
| 272 | |
| 273 - (void)ok:(id)sender { | |
| 274 DCHECK(delegate_); | |
| 275 delegate_->Closing(); | |
| 276 } | |
| 277 | |
| 278 - (void)onAllow:(id)sender { | |
| 279 DCHECK(delegate_); | |
| 280 delegate_->Accept(); | |
| 281 } | |
| 282 | |
| 283 - (void)onBlock:(id)sender { | |
| 284 DCHECK(delegate_); | |
| 285 delegate_->Deny(); | |
| 286 } | |
| 287 | |
| 288 - (void)onCustomize:(id)sender { | |
| 289 DCHECK(delegate_); | |
| 290 delegate_->SetCustomizationMode(); | |
| 291 } | |
| 292 | |
| 293 - (void)onCheckboxChanged:(id)sender { | |
| 294 DCHECK(delegate_); | |
| 295 NSButton* checkbox = base::mac::ObjCCastStrict<NSButton>(sender); | |
| 296 NSUInteger index = [checkboxes_ indexOfObject:checkbox]; | |
|
groby-ooo-7-16
2014/02/04 02:39:30
If you want to avoid indexOfObject:, you can inste
leng
2014/02/04 21:48:22
Done.
| |
| 297 DCHECK(index != NSNotFound); | |
| 298 delegate_->ToggleAccept(index, [checkbox state] == NSOnState); | |
| 299 } | |
| 300 | |
| 301 @end // implementation PermissionBubbleController | |
| OLD | NEW |