Chromium Code Reviews| Index: chrome/browser/ui/cocoa/permission_bubble_controller.mm |
| diff --git a/chrome/browser/ui/cocoa/permission_bubble_controller.mm b/chrome/browser/ui/cocoa/permission_bubble_controller.mm |
| new file mode 100644 |
| index 0000000000000000000000000000000000000000..aabb16f3762b8885eda619b9c919373fe95fd8ac |
| --- /dev/null |
| +++ b/chrome/browser/ui/cocoa/permission_bubble_controller.mm |
| @@ -0,0 +1,301 @@ |
| +// Copyright (c) 2014 The Chromium Authors. All rights reserved. |
| +// Use of this source code is governed by a BSD-style license that can be |
| +// found in the LICENSE file. |
| + |
| +#import "chrome/browser/ui/cocoa/permission_bubble_controller.h" |
| + |
| +#include <algorithm> |
| + |
| +#include "base/mac/foundation_util.h" |
| +#include "base/mac/mac_util.h" |
| +#include "base/strings/sys_string_conversions.h" |
| +#include "chrome/browser/ui/browser.h" |
| +#include "chrome/browser/ui/browser_finder.h" |
| +#import "chrome/browser/ui/chrome_style.h" |
| +#import "chrome/browser/ui/cocoa/browser_window_controller.h" |
| +#import "chrome/browser/ui/cocoa/hyperlink_text_view.h" |
| +#import "chrome/browser/ui/cocoa/info_bubble_view.h" |
| +#import "chrome/browser/ui/cocoa/info_bubble_window.h" |
| +#include "chrome/browser/ui/cocoa/permission_bubble_cocoa.h" |
| +#include "chrome/browser/ui/website_settings/permission_bubble_delegate.h" |
| +#include "chrome/browser/ui/website_settings/permission_bubble_view.h" |
| +#include "content/public/browser/user_metrics.h" |
| +#include "grit/generated_resources.h" |
| +#include "skia/ext/skia_utils_mac.h" |
| +#include "ui/base/l10n/l10n_util_mac.h" |
| + |
| +using base::UserMetricsAction; |
| + |
| +namespace { |
| +const CGFloat kHorizontalPadding = 20.0f; |
| +const CGFloat kVerticalPadding = 20.0f; |
| +const CGFloat kButtonPadding = 10.0f; |
| +const CGFloat kCheckboxYAdjustment = 2.0f; |
| + |
| +const CGFloat kButtonWidth = 80.0f; |
| +const CGFloat kButtonHeight = 30.0f; |
| + |
| +const CGFloat kFontSize = 15.0f; |
| +const base::char16 kBulletPoint = 0x2022; |
| +const int kButtonBackgroundColor = 239; |
| + |
| +} // namespace |
| + |
| +@interface PermissionBubbleController () |
| + |
| +// Called when the 'ok' button is pressed. |
| +- (void)ok:(id)sender; |
| + |
| +// Called when the 'allow' button is pressed. |
| +- (void)onAllow:(id)sender; |
| + |
| +// Called when the 'block' button is pressed. |
| +- (void)onBlock:(id)sender; |
| + |
| +// Called when the 'customize' button is pressed. |
| +- (void)onCustomize:(id)sender; |
| + |
| +// Called when a checkbox changes from checked to unchecked, or vice versa. |
| +- (void)onCheckboxChanged:(id)sender; |
| + |
| +@end |
| + |
| +@implementation PermissionBubbleController |
| + |
| +- (id)initWithParentWindow:(NSWindow*)parentWindow |
| + bridge:(PermissionBubbleCocoa*)bridge { |
| + DCHECK(parentWindow); |
| + DCHECK(bridge); |
| + base::scoped_nsobject<InfoBubbleWindow> window( |
| + [[InfoBubbleWindow alloc] initWithContentRect:NSMakeRect(0, 0, 240, 150) |
| + styleMask:NSBorderlessWindowMask |
| + backing:NSBackingStoreBuffered |
| + defer:NO]); |
| + [window setAllowedAnimations:info_bubble::kAnimateNone]; |
| + if ((self = [super initWithWindow:window |
| + parentWindow:parentWindow |
| + anchoredAt:NSZeroPoint])) { |
| + [self setShouldCloseOnResignKey:NO]; |
| + [[self bubble] setArrowLocation:info_bubble::kTopLeft]; |
| + bridge_ = bridge; |
| + } |
| + return self; |
| +} |
| + |
| +- (void)windowWillClose:(NSNotification*)notification { |
| + bridge_->OnBubbleClosing(); |
| + [super windowWillClose:notification]; |
| +} |
| + |
| +- (void)showAtAnchor:(NSPoint)anchorPoint |
| + withDelegate:(PermissionBubbleView::Delegate*)delegate |
| + forRequests:(const std::vector<PermissionBubbleDelegate*>&)requests |
| + acceptStates:(const std::vector<bool>&)acceptStates |
| + customizationMode:(BOOL)customizationMode { |
| + DCHECK(delegate); |
| + DCHECK(!customizationMode || (requests.size() == acceptStates.size())); |
| + delegate_ = delegate; |
| + |
| + NSView* contentView = [[self window] contentView]; |
| + DCHECK([[contentView subviews] count] == 0); |
| + |
| + BOOL singlePermission = requests.size() == 1; |
| + NSRect bubbleFrame = [[self window] frame]; |
| + CGFloat yOffset = 2 * kVerticalPadding + kButtonHeight; |
| + |
| + checkboxes_.reset(customizationMode ? [[NSMutableArray alloc] init] : nil); |
| + 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
|
| + base::scoped_nsobject<NSView> permissionView; |
| + if (customizationMode) { |
| + int index = it - requests.begin(); |
| + permissionView.reset( |
| + [[self checkboxForRequest:(*it) |
| + checked:acceptStates[index] ? YES : NO] retain]); |
| + [checkboxes_ addObject:permissionView]; |
| + } else { |
| + permissionView.reset([[self labelForRequest:(*it) |
| + isSingleRequest:singlePermission] retain]); |
| + } |
| + NSPoint origin = [permissionView frame].origin; |
| + origin.x += kHorizontalPadding; |
| + origin.y += yOffset; |
| + [permissionView setFrameOrigin:origin]; |
| + [contentView addSubview:permissionView]; |
| + |
| + yOffset += NSHeight([permissionView frame]); |
| + } |
| + |
| + if (!singlePermission && !customizationMode) { |
| + base::scoped_nsobject<NSView> customizeButton( |
| + [[self customizationButton] retain]); |
| + // The Y center should match the Y centers of the buttons. |
| + CGFloat customizeButtonYOffset = kVerticalPadding + |
| + (kButtonHeight - std::ceil(NSHeight([customizeButton frame])) * 0.5f); |
| + [customizeButton setFrameOrigin:NSMakePoint(kHorizontalPadding, |
| + customizeButtonYOffset)]; |
| + [contentView addSubview:customizeButton]; |
| + } |
| + |
| + // The maximum width of the above permissions will dictate the width of the |
| + // bubble. It is calculated here so that it can be used for the positioning |
| + // of the buttons. |
| + for (NSView* view in [contentView subviews]) |
| + bubbleFrame = NSUnionRect(bubbleFrame, [view frame]); |
| + 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
|
| + |
| + base::scoped_nsobject<NSView> allowOrOkButton; |
| + if (customizationMode) { |
| + allowOrOkButton.reset([[self buttonWithTitle:@"OK" |
| + action:@selector(ok:)] retain]); |
| + } else { |
| + allowOrOkButton.reset([[self buttonWithTitle:@"Allow" |
| + action:@selector(onAllow:)] retain]); |
| + } |
| + CGFloat xOrigin = NSWidth(bubbleFrame) - kButtonWidth - kHorizontalPadding; |
| + [allowOrOkButton setFrameOrigin:NSMakePoint(xOrigin, kVerticalPadding)]; |
| + [contentView addSubview:allowOrOkButton]; |
| + |
| + if (!customizationMode) { |
| + base::scoped_nsobject<NSView> blockButton( |
| + [[self buttonWithTitle:@"Block" action:@selector(onBlock:)] retain]); |
| + xOrigin = NSMinX([allowOrOkButton frame]) - NSWidth([blockButton frame]) - |
| + kButtonPadding; |
| + [blockButton setFrameOrigin:NSMakePoint(xOrigin, kVerticalPadding)]; |
| + [contentView addSubview:blockButton]; |
| + } |
| + |
| + if (!singlePermission) { |
| + base::scoped_nsobject<NSView> titleView( |
| + [[self titleForMultipleRequests] retain]); |
| + [contentView addSubview:titleView]; |
| + [titleView setFrameOrigin:NSMakePoint(kHorizontalPadding, |
| + kVerticalPadding + yOffset)]; |
| + yOffset += NSHeight([titleView frame]) + kVerticalPadding; |
| + } |
| + |
| + bubbleFrame.size.height = yOffset + kVerticalPadding; |
| + bubbleFrame = [[self window] frameRectForContentRect:bubbleFrame]; |
| + [[self window] setFrame:bubbleFrame display:NO]; |
| + |
| + [self setAnchorPoint:anchorPoint]; |
| + [self showWindow:nil]; |
| +} |
| + |
| +- (NSView*)labelForRequest:(PermissionBubbleDelegate*)request |
| + isSingleRequest:(BOOL)singleRequest { |
| + DCHECK(request); |
| + base::scoped_nsobject<NSTextField> permissionLabel( |
| + [[NSTextField alloc] initWithFrame:NSZeroRect]); |
| + base::string16 label; |
| + if (!singleRequest) { |
| + label.push_back(kBulletPoint); |
| + label.push_back(' '); |
| + } |
| + if (singleRequest) { |
| + // TODO(leng): Make the appropriate call when it's working. It should call |
| + // GetMessageText(), but it's not returning the correct string yet. |
| + label += request->GetMessageTextFragment(); |
| + } else { |
| + label += request->GetMessageTextFragment(); |
| + } |
| + [permissionLabel setDrawsBackground:NO]; |
| + [permissionLabel setBezeled:NO]; |
| + [permissionLabel setEditable:NO]; |
| + [permissionLabel setSelectable:NO]; |
| + [permissionLabel setStringValue:base::SysUTF16ToNSString(label)]; |
| + [permissionLabel sizeToFit]; |
| + return permissionLabel.autorelease(); |
| +} |
| + |
| +- (NSView*)titleForMultipleRequests { |
| + base::scoped_nsobject<NSTextField> titleView( |
| + [[NSTextField alloc] initWithFrame:NSZeroRect]); |
| + [titleView setDrawsBackground:NO]; |
| + [titleView setBezeled:NO]; |
| + [titleView setEditable:NO]; |
| + [titleView setSelectable:NO]; |
| + [titleView setStringValue:@"This site would like to:"]; |
| + [titleView setFont:[NSFont systemFontOfSize:kFontSize]]; |
| + [titleView sizeToFit]; |
| + return titleView.autorelease(); |
| +} |
| + |
| +- (NSView*)checkboxForRequest:(PermissionBubbleDelegate*)request |
| + checked:(BOOL)checked { |
| + DCHECK(request); |
| + base::scoped_nsobject<NSButton> checkbox( |
| + [[NSButton alloc] initWithFrame:NSZeroRect]); |
| + [checkbox setButtonType:NSSwitchButton]; |
| + base::string16 permission = request->GetMessageTextFragment(); |
| + [checkbox setTitle:base::SysUTF16ToNSString(permission)]; |
| + [checkbox setState:(checked ? NSOnState : NSOffState)]; |
| + [checkbox setTarget:self]; |
| + [checkbox setAction:@selector(onCheckboxChanged:)]; |
| + [checkbox sizeToFit]; |
| + [checkbox setFrameOrigin:NSMakePoint(0, kCheckboxYAdjustment)]; |
| + return checkbox.autorelease(); |
| +} |
| + |
| +- (NSView*)customizationButton { |
| + NSColor* linkColor = |
| + gfx::SkColorToCalibratedNSColor(chrome_style::GetLinkColor()); |
| + base::scoped_nsobject<NSButton> customizeButton( |
| + [[NSButton alloc] initWithFrame:NSZeroRect]); |
| + [customizeButton setButtonType:NSMomentaryChangeButton]; |
| + [customizeButton setAttributedTitle:[[NSAttributedString alloc] |
| + initWithString:@"Customize" |
| + attributes:@{ NSForegroundColorAttributeName : linkColor }]]; |
| + [customizeButton setTarget:self]; |
| + [customizeButton setAction:@selector(onCustomize:)]; |
| + [customizeButton sizeToFit]; |
| + return customizeButton.autorelease(); |
| +} |
| + |
| +- (NSView*)buttonWithTitle:(NSString*)title |
| + action:(SEL)action { |
| + NSRect frame = NSMakeRect(0, 0, kButtonWidth, kButtonHeight); |
| + NSColor* buttonBackgroundColor = |
| + gfx::SkColorToCalibratedNSColor(SkColorSetRGB(kButtonBackgroundColor, |
| + kButtonBackgroundColor, |
| + kButtonBackgroundColor)); |
| + |
| + base::scoped_nsobject<NSButton> button( |
| + [[NSButton alloc] initWithFrame:frame]); |
| + [button setButtonType:NSMomentaryPushInButton]; |
| + [button setTitle:title]; |
| + [button setTarget:self]; |
| + [button setAction:action]; |
| + [button setBordered:NO]; |
| + [[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
|
| + return button.autorelease(); |
| +} |
| + |
| +- (void)ok:(id)sender { |
| + DCHECK(delegate_); |
| + delegate_->Closing(); |
| +} |
| + |
| +- (void)onAllow:(id)sender { |
| + DCHECK(delegate_); |
| + delegate_->Accept(); |
| +} |
| + |
| +- (void)onBlock:(id)sender { |
| + DCHECK(delegate_); |
| + delegate_->Deny(); |
| +} |
| + |
| +- (void)onCustomize:(id)sender { |
| + DCHECK(delegate_); |
| + delegate_->SetCustomizationMode(); |
| +} |
| + |
| +- (void)onCheckboxChanged:(id)sender { |
| + DCHECK(delegate_); |
| + NSButton* checkbox = base::mac::ObjCCastStrict<NSButton>(sender); |
| + 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.
|
| + DCHECK(index != NSNotFound); |
| + delegate_->ToggleAccept(index, [checkbox state] == NSOnState); |
| +} |
| + |
| +@end // implementation PermissionBubbleController |