Chromium Code Reviews| Index: chrome/browser/ui/cocoa/website_settings/permission_bubble_controller.mm |
| diff --git a/chrome/browser/ui/cocoa/website_settings/permission_bubble_controller.mm b/chrome/browser/ui/cocoa/website_settings/permission_bubble_controller.mm |
| index 0b6f6f03f8b6c240922da6e39e02ff2e961e8f46..71832b1b701c59e09566ec1610d90aba7c25ec46 100644 |
| --- a/chrome/browser/ui/cocoa/website_settings/permission_bubble_controller.mm |
| +++ b/chrome/browser/ui/cocoa/website_settings/permission_bubble_controller.mm |
| @@ -6,6 +6,7 @@ |
| #include <algorithm> |
| +#include "base/mac/bind_objc_block.h" |
| #include "base/mac/foundation_util.h" |
| #include "base/mac/mac_util.h" |
| #include "base/strings/sys_string_conversions.h" |
| @@ -20,12 +21,15 @@ |
| #import "chrome/browser/ui/cocoa/info_bubble_view.h" |
| #import "chrome/browser/ui/cocoa/info_bubble_window.h" |
| #include "chrome/browser/ui/cocoa/website_settings/permission_bubble_cocoa.h" |
| +#include "chrome/browser/ui/cocoa/website_settings/permission_selector_button.h" |
| #include "chrome/browser/ui/cocoa/website_settings/split_block_button.h" |
| #include "chrome/browser/ui/website_settings/permission_bubble_request.h" |
| #include "chrome/browser/ui/website_settings/permission_bubble_view.h" |
| +#include "chrome/browser/ui/website_settings/permission_menu_model.h" |
| #include "content/public/browser/user_metrics.h" |
| #include "grit/generated_resources.h" |
| #include "skia/ext/skia_utils_mac.h" |
| +#import "ui/base/cocoa/menu_controller.h" |
| #include "ui/base/cocoa/window_size_constants.h" |
| #import "ui/base/cocoa/menu_controller.h" |
| #include "ui/base/l10n/l10n_util_mac.h" |
| @@ -39,9 +43,10 @@ const CGFloat kHorizontalPadding = 20.0f; |
| const CGFloat kVerticalPadding = 20.0f; |
| const CGFloat kButtonPadding = 10.0f; |
| const CGFloat kTitlePaddingX = 50.0f; |
| -const CGFloat kCheckboxYAdjustment = 2.0f; |
| +const CGFloat kTitleFontSize = 15.0f; |
| +const CGFloat kPermissionFontSize = 12.0f; |
| +const CGFloat kPermissionButtonTitleRightPadding = 4.0f; |
| -const CGFloat kFontSize = 15.0f; |
| class MenuDelegate : public ui::SimpleMenuModel::Delegate { |
| public: |
| explicit MenuDelegate(PermissionBubbleController* bubble) |
| @@ -50,10 +55,7 @@ class MenuDelegate : public ui::SimpleMenuModel::Delegate { |
| return false; |
| } |
| virtual bool IsCommandIdEnabled(int command_id) const OVERRIDE { |
| - // TODO(leng): Change this to true once setting the bubble to be |
| - // customizable works properly. Ideally, the bubble will alter its |
| - // contents, rather than reshowing completely. |
| - return false; |
| + return true; |
| } |
| virtual bool GetAcceleratorForCommandId( |
| int command_id, |
| @@ -70,6 +72,63 @@ class MenuDelegate : public ui::SimpleMenuModel::Delegate { |
| } // namespace |
| +// NSPopUpButton with a menu containing two items: allow and block. |
| +// One AllowBlockMenuButton is used for each requested permission, but only when |
| +// the permission bubble is in 'customize' mode. |
| +@interface AllowBlockMenuButton : NSPopUpButton { |
| + @private |
| + scoped_ptr<PermissionMenuModel> menuModel_; |
| + base::scoped_nsobject<MenuController> menuController_; |
| +} |
| + |
| +- (id)initForURL:(const GURL&)url |
| + allowed:(BOOL)allow |
| + index:(int)index |
| + delegate:(PermissionBubbleView::Delegate*)delegate; |
| +@end |
| + |
| +@implementation AllowBlockMenuButton |
| + |
| +- (id)initForURL:(const GURL&)url |
| + allowed:(BOOL)allow |
| + index:(int)index |
| + delegate:(PermissionBubbleView::Delegate*)delegate { |
| + if (self = [super initWithFrame:NSMakeRect(0, 0, 1, 1) pullsDown:NO]) { |
|
groby-ooo-7-16
2014/04/25 19:25:28
Why not NSZeroRect?
leng
2014/04/25 22:18:56
Done.
|
| + ContentSetting setting = |
| + allow ? CONTENT_SETTING_ALLOW : CONTENT_SETTING_BLOCK; |
| + [self setFont:[NSFont systemFontOfSize:kPermissionFontSize]]; |
| + [self setBordered:NO]; |
| + |
| + __block PermissionBubbleView::Delegate* blockDelegate = delegate; |
| + PermissionMenuModel::ChangeCallback changeCallback = |
| + base::BindBlock(^(const WebsiteSettingsUI::PermissionInfo& permission) { |
|
groby-ooo-7-16
2014/04/25 19:25:28
I just realized we created an ObjC block for the O
leng
2014/04/25 22:18:56
On 2014/04/25 19:25:28, groby wrote:
> I just real
|
| + blockDelegate->ToggleAccept( |
| + index, permission.setting == CONTENT_SETTING_ALLOW); |
| + }); |
| + |
| + menuModel_.reset(new PermissionMenuModel(url, setting, changeCallback)); |
| + menuController_.reset([[MenuController alloc] initWithModel:menuModel_.get() |
| + useWithPopUpButtonCell:NO]); |
| + [self setMenu:[menuController_ menu]]; |
| + [self selectItemAtIndex:menuModel_->GetIndexOfCommandId(setting)]; |
| + [self sizeToFit]; |
| + // Adjust the size to fit the current title. Using only -sizeToFit leaves |
| + // an ugly amount of whitespace between the title and the arrows. |
|
groby-ooo-7-16
2014/04/25 19:25:28
Ah. That's because NSPopUpButton's sizeToFit fits
leng
2014/04/25 22:18:56
Done.
|
| + // TODO(leng): This was copied from PermissionSelectorButton. Move to a |
| + // shared location, so that the code is not duplicated. |
| + NSDictionary* textAttributes = @{NSFontAttributeName : [self font]}; |
|
groby-ooo-7-16
2014/04/25 19:25:28
You could call
[[self attributedTitle] attribut
leng
2014/04/25 22:18:56
Done.
|
| + NSSize titleSize = [[self title] sizeWithAttributes:textAttributes]; |
| + NSRect frame = [self frame]; |
| + NSRect titleRect = [[self cell] titleRectForBounds:frame]; |
| + CGFloat width = titleSize.width + NSWidth(frame) - NSWidth(titleRect); |
| + [self setFrameSize:NSMakeSize(width + kPermissionButtonTitleRightPadding, |
| + NSHeight(frame))]; |
| + } |
| + return self; |
| +} |
| + |
| +@end |
| + |
| @interface PermissionBubbleController () |
| // Returns an autoreleased NSView displaying the icon and label for |request|. |
| @@ -79,10 +138,11 @@ class MenuDelegate : public ui::SimpleMenuModel::Delegate { |
| // requesting settings for |host|. |
| - (NSView*)titleWithHostname:(const std::string&)host; |
| -// Returns an autoreleased NSView displaying a checkbox for |request|. The |
| -// checkbox will be initialized as checked if |checked| is YES. |
| -- (NSView*)checkboxForRequest:(PermissionBubbleRequest*)request |
| - checked:(BOOL)checked; |
| +// Returns an autoreleased NSView displaying a menu for |request|. The |
| +// menu will be initialized as 'allow' if |allow| is YES. |
| +- (NSView*)menuForRequest:(PermissionBubbleRequest*)request |
| + atIndex:(int)index |
| + allow:(BOOL)allow; |
| // Returns an autoreleased NSView of a button with |title| and |action|. |
| - (NSView*)buttonWithTitle:(NSString*)title |
| @@ -98,10 +158,6 @@ class MenuDelegate : public ui::SimpleMenuModel::Delegate { |
| // Returns an autoreleased NSView displaying the close 'x' button. |
| - (NSView*)closeButton; |
| -// Sets the width of both |viewA| and |viewB| to be the larger of the |
| -// two views' widths. Does not change either view's origin or height. |
| -- (CGFloat)matchWidthsOf:(NSView*)viewA andOf:(NSView*)viewB; |
| - |
| // Called when the 'ok' button is pressed. |
| - (void)ok:(id)sender; |
| @@ -117,8 +173,13 @@ class MenuDelegate : public ui::SimpleMenuModel::Delegate { |
| // 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; |
| +// Sets the width of both |viewA| and |viewB| to be the larger of the |
| +// two views' widths. Does not change either view's origin or height. |
| ++ (CGFloat)matchWidthsOf:(NSView*)viewA andOf:(NSView*)viewB; |
| + |
| +// Sets the offset of |viewA| so that its vertical center is aligned with the |
| +// vertical center of |viewB|. |
| ++ (void)alignCenterOf:(NSView*)viewA verticallyToCenterOf:(NSView*)viewB; |
| @end |
| @@ -176,25 +237,32 @@ class MenuDelegate : public ui::SimpleMenuModel::Delegate { |
| CGFloat yOffset = 2 * kVerticalPadding + NSMaxY([allowOrOkButton frame]); |
| BOOL singlePermission = requests.size() == 1; |
| - checkboxes_.reset(customizationMode ? [[NSMutableArray alloc] init] : nil); |
| + base::scoped_nsobject<NSMutableArray> permissionMenus; |
| + if (customizationMode) |
| + permissionMenus.reset([[NSMutableArray alloc] init]); |
| + |
| for (auto it = requests.begin(); it != requests.end(); it++) { |
| - base::scoped_nsobject<NSView> permissionView; |
| - if (customizationMode) { |
| - int index = it - requests.begin(); |
| - permissionView.reset( |
| - [[self checkboxForRequest:(*it) |
| - checked:acceptStates[index] ? YES : NO] retain]); |
| - [base::mac::ObjCCastStrict<NSButton>(permissionView) setTag:index]; |
| - [checkboxes_ addObject:permissionView]; |
| - } else { |
| - permissionView.reset([[self labelForRequest:(*it)] retain]); |
| - } |
| + base::scoped_nsobject<NSView> permissionView( |
| + [[self labelForRequest:(*it)] retain]); |
| NSPoint origin = [permissionView frame].origin; |
| origin.x += kHorizontalPadding; |
| origin.y += yOffset; |
| [permissionView setFrameOrigin:origin]; |
| [contentView addSubview:permissionView]; |
| + if (customizationMode) { |
| + int index = it - requests.begin(); |
| + base::scoped_nsobject<NSView> menu( |
| + [[self menuForRequest:(*it) |
| + atIndex:index |
| + allow:acceptStates[index] ? YES : NO] retain]); |
| + // Align vertically. Horizontal alignment will be adjusted once the |
| + // widest permission is know. |
| + [PermissionBubbleController alignCenterOf:menu |
| + verticallyToCenterOf:permissionView]; |
| + [permissionMenus addObject:menu]; |
| + [contentView addSubview:menu]; |
| + } |
| yOffset += NSHeight([permissionView frame]); |
| } |
| @@ -207,6 +275,18 @@ class MenuDelegate : public ui::SimpleMenuModel::Delegate { |
| bubbleFrame, NSInsetRect([view frame], -kHorizontalPadding, 0)); |
| } |
| + if (customizationMode) { |
| + // Adjust the horizontal origin for each menu. |
| + CGFloat xOffset = NSWidth(bubbleFrame) - kHorizontalPadding; |
| + CGFloat maxMenuWidth = 0; |
| + for (NSView* view in permissionMenus.get()) { |
| + [view setFrameOrigin:NSMakePoint(xOffset, NSMinY([view frame]))]; |
| + maxMenuWidth = std::max(maxMenuWidth, NSWidth([view frame])); |
| + } |
| + // And add the menu width to the bubble's width. |
| + bubbleFrame.size.width += maxMenuWidth; |
| + } |
| + |
| base::scoped_nsobject<NSView> titleView( |
| [[self titleWithHostname:requests[0]->GetRequestingHostname().host()] |
| retain]); |
| @@ -242,7 +322,8 @@ class MenuDelegate : public ui::SimpleMenuModel::Delegate { |
| blockButton.reset([[self blockButton] retain]); |
| else |
| blockButton.reset([[self blockButtonWithCustomizeMenu] retain]); |
| - CGFloat width = [self matchWidthsOf:blockButton andOf:allowOrOkButton]; |
| + CGFloat width = [PermissionBubbleController matchWidthsOf:blockButton |
| + andOf:allowOrOkButton]; |
| // Ensure the allow/ok button is still in the correct position. |
| xOrigin = NSWidth(bubbleFrame) - width - kHorizontalPadding; |
| [allowOrOkButton setFrameOrigin:NSMakePoint(xOrigin, kVerticalPadding)]; |
| @@ -290,6 +371,7 @@ class MenuDelegate : public ui::SimpleMenuModel::Delegate { |
| [permissionLabel setBezeled:NO]; |
| [permissionLabel setEditable:NO]; |
| [permissionLabel setSelectable:NO]; |
| + [permissionLabel setFont:[NSFont systemFontOfSize:kPermissionFontSize]]; |
| [permissionLabel setStringValue:base::SysUTF16ToNSString(label)]; |
| [permissionLabel sizeToFit]; |
| [permissionLabel setFrameOrigin: |
| @@ -326,7 +408,7 @@ class MenuDelegate : public ui::SimpleMenuModel::Delegate { |
| [titleView setStringValue: |
| l10n_util::GetNSStringF(IDS_PERMISSIONS_BUBBLE_PROMPT, |
| base::UTF8ToUTF16(host))]; |
| - [titleView setFont:[NSFont systemFontOfSize:kFontSize]]; |
| + [titleView setFont:[NSFont systemFontOfSize:kTitleFontSize]]; |
| [titleView sizeToFit]; |
| NSRect titleFrame = [titleView frame]; |
| [titleView setFrameSize:NSMakeSize(NSWidth(titleFrame) + kTitlePaddingX, |
| @@ -334,20 +416,17 @@ class MenuDelegate : public ui::SimpleMenuModel::Delegate { |
| return titleView.autorelease(); |
| } |
| -- (NSView*)checkboxForRequest:(PermissionBubbleRequest*)request |
| - checked:(BOOL)checked { |
| +- (NSView*)menuForRequest:(PermissionBubbleRequest*)request |
| + atIndex:(int)index |
| + allow:(BOOL)allow { |
| 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(); |
| + DCHECK(delegate_); |
| + base::scoped_nsobject<AllowBlockMenuButton> button( |
| + [[AllowBlockMenuButton alloc] initForURL:request->GetRequestingHostname() |
| + allowed:allow |
| + index:index |
| + delegate:delegate_]); |
| + return button.autorelease(); |
| } |
| - (NSView*)buttonWithTitle:(NSString*)title |
| @@ -389,15 +468,6 @@ class MenuDelegate : public ui::SimpleMenuModel::Delegate { |
| return button.autorelease(); |
| } |
| -- (CGFloat)matchWidthsOf:(NSView*)viewA andOf:(NSView*)viewB { |
| - NSRect frameA = [viewA frame]; |
| - NSRect frameB = [viewB frame]; |
| - CGFloat width = std::max(NSWidth(frameA), NSWidth(frameB)); |
| - [viewA setFrameSize:NSMakeSize(width, NSHeight(frameA))]; |
| - [viewB setFrameSize:NSMakeSize(width, NSHeight(frameB))]; |
| - return width; |
| -} |
| - |
| - (void)ok:(id)sender { |
| DCHECK(delegate_); |
| delegate_->Closing(); |
| @@ -423,15 +493,26 @@ class MenuDelegate : public ui::SimpleMenuModel::Delegate { |
| delegate_->SetCustomizationMode(); |
| } |
| -- (void)onCheckboxChanged:(id)sender { |
| - DCHECK(delegate_); |
| - NSButton* checkbox = base::mac::ObjCCastStrict<NSButton>(sender); |
| - delegate_->ToggleAccept([checkbox tag], [checkbox state] == NSOnState); |
| -} |
| - |
| - (void)onMenuItemClicked:(int)commandId { |
| DCHECK(commandId == 0); |
| [self onCustomize:nil]; |
| } |
| ++ (CGFloat)matchWidthsOf:(NSView*)viewA andOf:(NSView*)viewB { |
| + NSRect frameA = [viewA frame]; |
| + NSRect frameB = [viewB frame]; |
| + CGFloat width = std::max(NSWidth(frameA), NSWidth(frameB)); |
| + [viewA setFrameSize:NSMakeSize(width, NSHeight(frameA))]; |
| + [viewB setFrameSize:NSMakeSize(width, NSHeight(frameB))]; |
| + return width; |
| +} |
| + |
| ++ (void)alignCenterOf:(NSView*)viewA verticallyToCenterOf:(NSView*)viewB { |
| + NSRect frameA = [viewA frame]; |
| + NSRect frameB = [viewB frame]; |
| + frameA.origin.y = |
| + NSMinY(frameB) + std::floor((NSHeight(frameB) - NSHeight(frameA)) / 2); |
| + [viewA setFrameOrigin:frameA.origin]; |
| +} |
| + |
| @end // implementation PermissionBubbleController |