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