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 |