Chromium Code Reviews| Index: chrome/browser/ui/cocoa/browser/password_generation_bubble_controller.mm |
| diff --git a/chrome/browser/ui/cocoa/browser/password_generation_bubble_controller.mm b/chrome/browser/ui/cocoa/browser/password_generation_bubble_controller.mm |
| new file mode 100644 |
| index 0000000000000000000000000000000000000000..cbefefa135585e1bf11c76fb25bb4bbc63a6ef66 |
| --- /dev/null |
| +++ b/chrome/browser/ui/cocoa/browser/password_generation_bubble_controller.mm |
| @@ -0,0 +1,378 @@ |
| +// Copyright (c) 2012 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/browser/password_generation_bubble_controller.h" |
| + |
| +#include "base/sys_string_conversions.h" |
| +#include "base/mac/foundation_util.h" |
| +#include "chrome/common/autofill_messages.h" |
| +#include "chrome/browser/autofill/password_generator.h" |
| +#include "chrome/browser/password_manager/password_manager.h" |
| +#include "chrome/browser/ui/browser.h" |
| +#include "chrome/browser/ui/browser_window.h" |
| +#import "chrome/browser/ui/cocoa/info_bubble_view.h" |
| +#import "chrome/browser/ui/cocoa/info_bubble_window.h" |
| +#include "chrome/browser/ui/cocoa/key_equivalent_constants.h" |
| +#import "chrome/browser/ui/cocoa/styled_text_field_cell.h" |
| +#import "chrome/browser/ui/cocoa/tracking_area.h" |
| +#include "content/public/browser/render_view_host.h" |
| +#include "content/public/common/password_form.h" |
| +#include "grit/generated_resources.h" |
| +#include "grit/theme_resources.h" |
| +#include "ui/base/l10n/l10n_util_mac.h" |
| +#include "ui/base/resource/resource_bundle.h" |
| + |
| +namespace { |
| + |
| +// Size of the border in the bubble. |
| +const CGFloat kBorderSize = 9.0; |
| + |
| +// Visible size of the textfield. |
| +const CGFloat kTextFieldHeight = 20.0; |
| +const CGFloat kTextFieldWidth = 172.0; |
| + |
| +// Frame padding necessary to make the textfield the correct visible size. |
| +const CGFloat kTextFieldTopPadding = 3.0; |
| + |
| +// Visible size of the button |
| +const CGFloat kButtonWidth = 63.0; |
| +const CGFloat kButtonHeight = 20.0; |
| + |
| +// Padding that is added to the frame around the button to make it the |
| +// correct visible size. Determined via visual inspection. |
| +const CGFloat kButtonHorizontalPadding = 6.5; |
| +const CGFloat kButtonVerticalPadding = 3.0; |
| + |
| +// Visible size of the title. |
| +const CGFloat kTitleWidth = 170.0; |
| +const CGFloat kTitleHeight = 15.0; |
| + |
| +// Space between the title and the textfield. |
| +const CGFloat kVerticalSpacing = 13.0; |
| + |
| +// Space between the textfield and the button. |
| +const CGFloat kHorizontalSpacing = 4.0; |
| + |
| +// We don't actually want the border to be kBorderSize on top as there is |
| +// whitespace in the title text that makes it looks substantially bigger. |
| +const CGFloat kTopBorderOffset = 3.0; |
| + |
| +const CGFloat kIconSize = 26.0; |
| + |
| +} // namespace |
| + |
| +// Customized StyledTextFieldCell to display one button decoration that changes |
| +// on hover. |
| +@interface PasswordGenerationTextFieldCell : StyledTextFieldCell { |
| + @private |
| + PasswordGenerationBubbleController* controller_; |
| + BOOL hovering_; |
| + scoped_nsobject<NSImage> normalImage_; |
| + scoped_nsobject<NSImage> hoverImage_; |
| +} |
| + |
| +- (void)setUpWithController:(PasswordGenerationBubbleController*)controller |
| + normalImage:(NSImage*)normalImage |
| + hoverImage:(NSImage*)hoverImage; |
| +- (void)mouseEntered:(NSEvent*)theEvent |
| + inView:(PasswordGenerationTextField*)controlView; |
| +- (void)mouseExited:(NSEvent*)theEvent |
| + inView:(PasswordGenerationTextField*)controlView; |
| +- (BOOL)mouseDown:(NSEvent*)theEvent |
| + inView:(PasswordGenerationTextField*)controlView; |
| +- (void)setUpTrackingAreaInRect:(NSRect)frame |
| + ofView:(PasswordGenerationTextField*)controlView; |
| +// Exposed for testing. |
| +- (NSRect)getIconFrame:(NSRect)cellFrame; |
| +@end |
| + |
| +@implementation PasswordGenerationTextField |
| + |
| ++ (Class)cellClass { |
| + return [PasswordGenerationTextFieldCell class]; |
| +} |
| + |
| +- (PasswordGenerationTextFieldCell*)cell { |
| + return base::mac::ObjCCastStrict<PasswordGenerationTextFieldCell>( |
| + [super cell]); |
| +} |
| + |
| +- (id)initWithFrame:(NSRect)frame |
| + withController:(PasswordGenerationBubbleController*)controller |
| + normalImage:(NSImage*)normalImage |
| + hoverImage:(NSImage*)hoverImage { |
| + self = [super initWithFrame:frame]; |
| + if (self) { |
| + PasswordGenerationTextFieldCell* cell = [self cell]; |
| + [cell setUpWithController:controller |
| + normalImage:normalImage |
| + hoverImage:hoverImage]; |
| + [cell setUpTrackingAreaInRect:[self bounds] ofView:self]; |
| + } |
| + return self; |
| +} |
| + |
| +- (void)mouseEntered:(NSEvent*)theEvent { |
| + [[self cell] mouseEntered:theEvent inView:self]; |
| +} |
| + |
| +- (void)mouseExited:(NSEvent*)theEvent { |
| + [[self cell] mouseExited:theEvent inView:self]; |
| +} |
| + |
| +- (void)mouseDown:(NSEvent*)theEvent { |
|
Scott Hess - ex-Googler
2012/11/28 20:01:06
I'm looking at this and wondering what happens if
Garrett Casto
2012/11/30 20:56:26
Changed.
|
| + // Let the cell handle the click if it's in the decoration. |
| + if (![[self cell] mouseDown:theEvent inView:self]) { |
| + [[self currentEditor] mouseDown:theEvent]; |
| + } |
| +} |
| + |
| +- (NSRect)getIconFrame { |
| + return [[self cell] getIconFrame:[self bounds]]; |
| +} |
| + |
| +@end |
| + |
| +@implementation PasswordGenerationTextFieldCell |
| + |
| +- (void)setUpWithController:(PasswordGenerationBubbleController*)controller |
| + normalImage:(NSImage*)normalImage |
| + hoverImage:(NSImage*)hoverImage { |
| + controller_ = controller; |
| + hovering_ = NO; |
| + normalImage_.reset([normalImage retain]); |
| + hoverImage_.reset([hoverImage retain]); |
| + [self setLineBreakMode:NSLineBreakByTruncatingTail]; |
| + [self setTruncatesLastVisibleLine:YES]; |
| +} |
| + |
| +- (void)splitFrame:(NSRect*)cellFrame toIconFrame:(NSRect*)iconFrame { |
| + NSDivideRect(*cellFrame, iconFrame, cellFrame, |
| + kIconSize, NSMaxXEdge); |
| +} |
| + |
| +- (NSRect)getIconFrame:(NSRect)cellFrame { |
| + NSRect iconFrame; |
| + [self splitFrame:&cellFrame toIconFrame:&iconFrame]; |
| + return iconFrame; |
| +} |
| + |
| +- (NSRect)getTextFrame:(NSRect)cellFrame { |
| + NSRect iconFrame; |
| + [self splitFrame:&cellFrame toIconFrame:&iconFrame]; |
| + return cellFrame; |
| +} |
| + |
| +- (BOOL)eventIsInDecoration:(NSEvent*)theEvent |
| + inView:(PasswordGenerationTextField*)controlView { |
| + NSPoint mouseLocation = [controlView convertPoint:[theEvent locationInWindow] |
| + fromView:nil]; |
| + NSRect cellFrame = [controlView bounds]; |
| + return NSMouseInRect(mouseLocation, |
| + [self getIconFrame:cellFrame], |
| + [controlView isFlipped]); |
| +} |
| + |
| +- (void)mouseEntered:(NSEvent*)theEvent |
| + inView:(PasswordGenerationTextField*)controlView { |
| + hovering_ = YES; |
| + [controlView setNeedsDisplay:YES]; |
| +} |
| + |
| +- (void)mouseExited:(NSEvent*)theEvent |
| + inView:(PasswordGenerationTextField*)controlView { |
| + hovering_ = NO; |
| + [controlView setNeedsDisplay:YES]; |
| +} |
| + |
| +- (BOOL)mouseDown:(NSEvent*)theEvent |
| + inView:(PasswordGenerationTextField*)controlView { |
| + if ([self eventIsInDecoration:theEvent inView:controlView]) { |
| + [controller_ regeneratePassword]; |
| + return YES; |
| + } |
| + return NO; |
| +} |
| + |
| +- (NSImage*)getImage { |
| + if (hovering_) |
| + return hoverImage_; |
| + return normalImage_; |
| +} |
| + |
| +- (NSRect)adjustFrameForFrame:(NSRect)frame { |
| + // By default, there appears to be a 2 pixel gap between what is considered |
| + // part of the textFrame and what is considered part of the icon. |
| + // TODO(gcasto): This really should be fixed in StyledTextFieldCell, as it |
| + // looks like the location bar also suffers from this issue. |
| + frame.size.width += 2; |
|
Scott Hess - ex-Googler
2012/11/28 20:01:06
Per comment I made probably against an earlier pat
Garrett Casto
2012/11/30 20:56:26
Looks right to me.
|
| + return frame; |
| +} |
| + |
| +- (NSRect)textFrameForFrame:(NSRect)cellFrame { |
| + // Baseclass insets the rect by baselineAdjust. |
| + NSRect textFrame = [super textFrameForFrame:cellFrame]; |
| + textFrame = [self getTextFrame:textFrame]; |
| + return [self adjustFrameForFrame:textFrame]; |
| +} |
| + |
| +- (NSRect)textCursorFrameForFrame:(NSRect)cellFrame { |
| + NSRect textFrame = [self getTextFrame:cellFrame]; |
| + return [self adjustFrameForFrame:textFrame]; |
| +} |
| + |
| +- (void)drawInteriorWithFrame:(NSRect)cellFrame inView:(NSView*)controlView { |
| + NSImage* image = [self getImage]; |
| + NSRect iconFrame = [self getIconFrame:cellFrame]; |
| + // Center the image in the available space. At the moment the image is |
| + // slightly larger than the frame so we crop it. |
| + // Offset the full difference on the left hand side since the border on the |
| + // right takes up some space. Offset half the vertical difference on the |
| + // bottom so that the image stays vertically centered. |
| + const CGFloat x_offset = [image size].width - NSWidth(iconFrame); |
| + const CGFloat y_offset = ([image size].height - (NSHeight(iconFrame))) / 2.0; |
|
Scott Hess - ex-Googler
2012/11/28 20:01:06
xOffset, yOffset.
Garrett Casto
2012/11/30 20:56:26
Done.
|
| + NSRect croppedRect = NSMakeRect(x_offset, |
| + y_offset, |
| + NSWidth(iconFrame), |
| + NSHeight(iconFrame)); |
| + |
| + [image drawInRect:iconFrame |
| + fromRect:croppedRect |
| + operation:NSCompositeSourceOver |
| + fraction:1.0 |
| + respectFlipped:YES |
| + hints:nil]; |
| + |
| + [super drawInteriorWithFrame:cellFrame inView:controlView]; |
| +} |
| + |
| +- (void)setUpTrackingAreaInRect:(NSRect)frame |
| + ofView:(PasswordGenerationTextField*)view { |
| + NSRect iconFrame = [self getIconFrame:frame]; |
| + scoped_nsobject<CrTrackingArea> area( |
| + [[CrTrackingArea alloc] initWithRect:iconFrame |
| + options:NSTrackingMouseEnteredAndExited | |
| + NSTrackingActiveAlways |
| + owner:view |
| + userInfo:nil]); |
| + [view addTrackingArea:area]; |
| +} |
| + |
| +- (CGFloat)baselineAdjust { |
| + return 1.0; |
| +} |
| + |
| +- (CGFloat)cornerRadius { |
| + return 4.0; |
| +} |
| + |
| +- (BOOL)shouldDrawBezel { |
| + return YES; |
| +} |
| + |
| +@end |
| + |
| +@implementation PasswordGenerationBubbleController |
| + |
| +- (id)initWithWindow:(NSWindow*)parentWindow |
| + anchoredAt:(NSPoint)point |
| + renderViewHost:(content::RenderViewHost*)renderViewHost |
| + passwordManager:(PasswordManager*)passwordManager |
| + usingGenerator:(autofill::PasswordGenerator*)passwordGenerator |
| + forForm:(const content::PasswordForm&)form { |
| + int width = (kBorderSize*2 + |
| + kTextFieldWidth + |
| + kHorizontalSpacing + |
| + kButtonWidth); |
| + int height = (kBorderSize*2 + |
| + kTextFieldHeight + |
| + kVerticalSpacing + |
| + kTitleHeight - |
| + kTopBorderOffset + |
| + info_bubble::kBubbleArrowHeight); |
|
Scott Hess - ex-Googler
2012/11/28 20:01:06
These should probably both be CGFloat.
Garrett Casto
2012/11/30 20:56:26
Done.
|
| + NSRect contentRect = NSMakeRect(0, 0, width, height); |
| + scoped_nsobject<InfoBubbleWindow> window( |
| + [[InfoBubbleWindow alloc] initWithContentRect:contentRect |
| + styleMask:NSBorderlessWindowMask |
| + backing:NSBackingStoreBuffered |
| + defer:NO]); |
| + if (self = [super initWithWindow:window |
| + parentWindow:parentWindow |
| + anchoredAt:point]) { |
| + passwordGenerator_ = passwordGenerator; |
| + renderViewHost_ = renderViewHost; |
| + passwordManager_ = passwordManager; |
| + form_ = form; |
| + [[self bubble] setArrowLocation:info_bubble::kTopLeft]; |
| + [self performLayout]; |
| + } |
| + |
| + return self; |
| +} |
| + |
| +- (void)performLayout { |
| + NSView* contentView = [[self window] contentView]; |
| + ResourceBundle& rb = ResourceBundle::GetSharedInstance(); |
| + |
| + textField_ = |
| + [[PasswordGenerationTextField alloc] |
| + initWithFrame:NSMakeRect(kBorderSize, |
| + kBorderSize, |
| + kTextFieldWidth, |
| + kTextFieldHeight + kTextFieldTopPadding) |
| + withController:self |
| + normalImage:rb.GetNativeImageNamed(IDR_RELOAD_DIMMED).ToNSImage() |
| + hoverImage:rb.GetNativeImageNamed(IDR_RELOAD).ToNSImage()]; |
| + gfx::Font smallBoldFont = |
| + rb.GetFont(ResourceBundle::SmallFont).DeriveFont(0, gfx::Font::BOLD); |
| + [textField_ setFont:smallBoldFont.GetNativeFont()]; |
| + [textField_ |
| + setStringValue:base::SysUTF8ToNSString(passwordGenerator_->Generate())]; |
| + [contentView addSubview:textField_]; |
| + |
| + int button_x = (kBorderSize + |
| + kTextFieldWidth + |
| + kHorizontalSpacing - |
| + kButtonVerticalPadding); |
|
Scott Hess - ex-Googler
2012/11/28 20:01:06
CGFloat here, too. kButtonVerticalPadding when ca
Garrett Casto
2012/11/30 20:56:26
Yep, good catch. I was trying to figure out why th
|
| + int button_y = kBorderSize - kButtonVerticalPadding; |
| + NSButton* button = |
| + [[NSButton alloc] initWithFrame:NSMakeRect( |
| + button_x, |
| + button_y, |
| + kButtonWidth + 2 * kButtonHorizontalPadding, |
| + kButtonHeight + 2 * kButtonVerticalPadding)]; |
| + [button setBezelStyle:NSRoundedBezelStyle]; |
| + [button setTitle:l10n_util::GetNSString(IDS_PASSWORD_GENERATION_BUTTON_TEXT)]; |
| + [button setTarget:self]; |
| + [button setAction:@selector(fillPassword:)]; |
| + [contentView addSubview:button]; |
| + |
| + NSTextField* title = [[NSTextField alloc] |
| + initWithFrame:NSMakeRect( |
| + kBorderSize, |
| + kBorderSize + kTextFieldHeight + kVerticalSpacing, |
|
Scott Hess - ex-Googler
2012/11/28 20:01:06
As with the earlier comment, this could be NSMaxY(
Garrett Casto
2012/11/30 20:56:26
This isn't equivalent. The way I've been trying to
|
| + kTitleWidth, |
| + kTitleHeight)]; |
| + [title setEditable:NO]; |
| + [title setBordered:NO]; |
| + [title setStringValue:l10n_util::GetNSString( |
| + IDS_PASSWORD_GENERATION_BUBBLE_TITLE)]; |
| + [contentView addSubview:title]; |
| +} |
| + |
| +- (IBAction)fillPassword:(id)sender { |
| + renderViewHost_->Send( |
| + new AutofillMsg_GeneratedPasswordAccepted( |
| + renderViewHost_->GetRoutingID(), |
| + base::SysNSStringToUTF16([textField_ stringValue]))); |
| + passwordManager_->SetFormHasGeneratedPassword(form_); |
| + [self close]; |
| +} |
| + |
| +- (void)regeneratePassword { |
| + [textField_ |
| + setStringValue:base::SysUTF8ToNSString(passwordGenerator_->Generate())]; |
| +} |
| + |
| +@end |