Index: chrome/browser/ui/cocoa/passwords/manage_password_item_view_controller.mm |
diff --git a/chrome/browser/ui/cocoa/passwords/manage_password_item_view_controller.mm b/chrome/browser/ui/cocoa/passwords/manage_password_item_view_controller.mm |
index 878cf24161a45f9d4c16f6ab2c409b9aa3add0d1..f2a4b76414d3985662e1b926ec776491832c6bc9 100644 |
--- a/chrome/browser/ui/cocoa/passwords/manage_password_item_view_controller.mm |
+++ b/chrome/browser/ui/cocoa/passwords/manage_password_item_view_controller.mm |
@@ -7,17 +7,68 @@ |
#include "base/logging.h" |
#include "base/strings/string16.h" |
#include "base/strings/sys_string_conversions.h" |
+#include "chrome/browser/ui/chrome_style.h" |
#import "chrome/browser/ui/cocoa/passwords/manage_passwords_bubble_content_view_controller.h" |
#include "chrome/browser/ui/passwords/manage_passwords_bubble_model.h" |
+#include "grit/generated_resources.h" |
#include "skia/ext/skia_utils_mac.h" |
+#import "ui/base/cocoa/controls/hyperlink_button_cell.h" |
+#import "ui/base/cocoa/hover_image_button.h" |
+#include "ui/base/l10n/l10n_util.h" |
+#include "ui/gfx/image/image.h" |
#include "ui/native_theme/common_theme.h" |
+#include "ui/resources/grit/ui_resources.h" |
#include "ui/views/layout/layout_constants.h" |
using namespace password_manager::mac::ui; |
namespace { |
-static const CGFloat kBorderWidth = 1; |
+const CGFloat kBorderWidth = 1; |
+const SkColor kHoverColor = SkColorSetARGBInline(0xFF, 0xEB, 0xEB, 0xEB); |
+ |
+NSColor* HoverColor() { |
+ return gfx::SkColorToCalibratedNSColor(kHoverColor); |
+} |
+ |
+NSFont* LabelFont() { |
+ return [NSFont systemFontOfSize:[NSFont smallSystemFontSize]]; |
+} |
+ |
+NSSize LabelSize(int resourceID) { |
+ return [l10n_util::GetNSString(resourceID) |
+ sizeWithAttributes:@{NSFontAttributeName : LabelFont()}]; |
+} |
+ |
+CGFloat FirstFieldWidth() { |
+ const CGFloat undoExplanationWidth = |
+ LabelSize(IDS_MANAGE_PASSWORDS_DELETED).width; |
+ const CGFloat kUsernameWidth = |
+ ManagePasswordsBubbleModel::UsernameFieldWidth(); |
+ const CGFloat width = std::max(kUsernameWidth, undoExplanationWidth); |
+ return width; |
+} |
+ |
+CGFloat SecondFieldWidth() { |
+ const CGFloat undoLinkWidth = |
+ LabelSize(IDS_MANAGE_PASSWORDS_UNDO).width; |
+ const CGFloat kPasswordWidth = |
+ ManagePasswordsBubbleModel::PasswordFieldWidth(); |
+ const CGFloat width = std::max(kPasswordWidth, undoLinkWidth); |
+ return width; |
+} |
+ |
+CGFloat ItemWidth() { |
+ const CGFloat width = |
+ kFramePadding + |
+ FirstFieldWidth() + |
+ views::kItemLabelSpacing + |
+ SecondFieldWidth() + |
+ views::kItemLabelSpacing + |
+ chrome_style::GetCloseButtonSize() + |
+ kFramePadding; |
+ return width; |
+} |
void InitLabel(NSTextField* textField, const base::string16& text) { |
[textField setStringValue:base::SysUTF16ToNSString(text)]; |
@@ -25,34 +76,156 @@ void InitLabel(NSTextField* textField, const base::string16& text) { |
[textField setSelectable:NO]; |
[textField setDrawsBackground:NO]; |
[textField setBezeled:NO]; |
- NSFont* font = [NSFont systemFontOfSize:[NSFont smallSystemFontSize]]; |
- [textField setFont:font]; |
+ [textField setFont:LabelFont()]; |
[textField sizeToFit]; |
- // TODO(dconnelly): Handle max width. |
} |
-NSTextField* UsernameLabel(const base::string16& text) { |
+NSTextField* Label(const base::string16& text) { |
base::scoped_nsobject<NSTextField> textField( |
[[NSTextField alloc] initWithFrame:NSZeroRect]); |
InitLabel(textField, text); |
return textField.autorelease(); |
} |
+NSTextField* UsernameLabel(const base::string16& text) { |
+ NSTextField* textField = Label(text); |
+ [textField |
+ setFrameSize:NSMakeSize(FirstFieldWidth(), NSHeight([textField frame]))]; |
+ return textField; |
+} |
+ |
NSSecureTextField* PasswordLabel(const base::string16& text) { |
base::scoped_nsobject<NSSecureTextField> textField( |
[[NSSecureTextField alloc] initWithFrame:NSZeroRect]); |
InitLabel(textField, text); |
+ [textField |
+ setFrameSize:NSMakeSize(SecondFieldWidth(), NSHeight([textField frame]))]; |
return textField.autorelease(); |
} |
} // namespace |
+@implementation ManagePasswordItemUndoView |
+- (id)initWithTarget:(id)target action:(SEL)action { |
+ if ((self = [super init])) { |
+ // The button should look like a link. |
+ undoButton_.reset([[NSButton alloc] initWithFrame:NSZeroRect]); |
+ base::scoped_nsobject<HyperlinkButtonCell> cell([[HyperlinkButtonCell alloc] |
+ initTextCell:l10n_util::GetNSString(IDS_MANAGE_PASSWORDS_UNDO)]); |
+ [cell setControlSize:NSSmallControlSize]; |
+ [cell setShouldUnderline:NO]; |
+ [cell setUnderlineOnHover:NO]; |
+ [cell setTextColor:gfx::SkColorToCalibratedNSColor( |
+ chrome_style::GetLinkColor())]; |
+ [undoButton_ setCell:cell.get()]; |
+ [undoButton_ sizeToFit]; |
+ [undoButton_ setTarget:target]; |
+ [undoButton_ setAction:action]; |
+ |
+ const CGFloat width = ItemWidth(); |
+ CGFloat curX = kFramePadding; |
+ CGFloat curY = views::kRelatedControlVerticalSpacing; |
+ |
+ // Add the explanation text. |
+ NSTextField* label = |
+ Label(l10n_util::GetStringUTF16(IDS_MANAGE_PASSWORDS_DELETED)); |
+ [label setFrameOrigin:NSMakePoint(curX, curY)]; |
+ [self addSubview:label]; |
+ |
+ // The undo button should be right-aligned. |
+ curX = width - kFramePadding - NSWidth([undoButton_ frame]); |
+ [undoButton_ setFrameOrigin:NSMakePoint(curX, curY)]; |
+ [self addSubview:undoButton_ ]; |
+ |
+ // Move to the top-right of the delete button. |
+ curX = NSMaxX([undoButton_ frame]) + kFramePadding; |
+ curY = NSMaxY([undoButton_ frame]) + views::kRelatedControlVerticalSpacing; |
+ |
+ // Update the frame. |
+ DCHECK_EQ(width, curX); |
+ [self setFrameSize:NSMakeSize(curX, curY)]; |
+ } |
+ return self; |
+} |
+@end |
+ |
+@implementation ManagePasswordItemUndoView (Testing) |
+- (NSButton*)undoButton { |
+ return undoButton_.get(); |
+} |
+@end |
+ |
+@implementation ManagePasswordItemManageView |
+- (id)initWithForm:(const autofill::PasswordForm&)form |
+ target:(id)target |
+ action:(SEL)action { |
+ if ((self = [super init])) { |
+ deleteButton_.reset([[HoverImageButton alloc] initWithFrame:NSZeroRect]); |
+ [deleteButton_ setFrameSize:NSMakeSize(chrome_style::GetCloseButtonSize(), |
+ chrome_style::GetCloseButtonSize())]; |
+ [deleteButton_ setBordered:NO]; |
+ [[deleteButton_ cell] setHighlightsBy:NSNoCellMask]; |
+ ui::ResourceBundle& bundle = ui::ResourceBundle::GetSharedInstance(); |
+ [deleteButton_ |
+ setDefaultImage:bundle.GetImageNamed(IDR_CLOSE_2).ToNSImage()]; |
+ [deleteButton_ |
+ setHoverImage:bundle.GetImageNamed(IDR_CLOSE_2_H).ToNSImage()]; |
+ [deleteButton_ |
+ setPressedImage:bundle.GetImageNamed(IDR_CLOSE_2_P).ToNSImage()]; |
+ [deleteButton_ setTarget:target]; |
+ [deleteButton_ setAction:action]; |
+ |
+ const CGFloat width = ItemWidth(); |
+ CGFloat curX = kFramePadding; |
+ CGFloat curY = views::kRelatedControlVerticalSpacing; |
+ |
+ // Add the username. |
+ usernameField_.reset([UsernameLabel(form.username_value) retain]); |
+ [usernameField_ setFrameOrigin:NSMakePoint(curX, curY)]; |
+ [self addSubview:usernameField_]; |
+ |
+ // Move to the right of the username and add the password. |
+ curX = NSMaxX([usernameField_ frame]) + views::kItemLabelSpacing; |
+ passwordField_.reset([PasswordLabel(form.password_value) retain]); |
+ [passwordField_ setFrameOrigin:NSMakePoint(curX, curY)]; |
+ [self addSubview:passwordField_]; |
+ |
+ // The delete button should be right-aligned. |
+ curX = width - kFramePadding - NSWidth([deleteButton_ frame]); |
+ [deleteButton_ setFrameOrigin:NSMakePoint(curX, curY)]; |
+ [self addSubview:deleteButton_]; |
+ |
+ // Move to the top-right of the delete button. |
+ curX = NSMaxX([deleteButton_ frame]) + kFramePadding; |
+ curY = |
+ NSMaxY([deleteButton_ frame]) + views::kRelatedControlVerticalSpacing; |
+ |
+ // Update the frame. |
+ DCHECK_EQ(width, curX); |
+ [self setFrameSize:NSMakeSize(curX, curY)]; |
+ } |
+ return self; |
+} |
+@end |
+ |
+@implementation ManagePasswordItemManageView (Testing) |
+- (NSTextField*)usernameField { |
+ return usernameField_.get(); |
+} |
+- (NSSecureTextField*)passwordField { |
+ return passwordField_.get(); |
+} |
+- (NSButton*)deleteButton { |
+ return deleteButton_.get(); |
+} |
+@end |
+ |
@implementation ManagePasswordItemPendingView |
- (id)initWithForm:(const autofill::PasswordForm&)form { |
if ((self = [super initWithFrame:NSZeroRect])) { |
- CGFloat curX = 0; |
- CGFloat curY = 0; |
+ CGFloat curX = kFramePadding; |
+ CGFloat curY = views::kRelatedControlVerticalSpacing; |
// Add the username. |
usernameField_.reset([UsernameLabel(form.username_value) retain]); |
@@ -60,17 +233,17 @@ NSSecureTextField* PasswordLabel(const base::string16& text) { |
[self addSubview:usernameField_]; |
// Move to the right of the username and add the password. |
- curX += NSWidth([usernameField_ frame]) + views::kItemLabelSpacing; |
+ curX = NSMaxX([usernameField_ frame]) + views::kItemLabelSpacing; |
passwordField_.reset([PasswordLabel(form.password_value) retain]); |
[passwordField_ setFrameOrigin:NSMakePoint(curX, curY)]; |
[self addSubview:passwordField_]; |
// Move to the top-right of the password. |
- curX = NSMaxX([passwordField_ frame]); |
- curY = NSMaxY([passwordField_ frame]); |
+ curY = |
+ NSMaxY([passwordField_ frame]) + views::kRelatedControlVerticalSpacing; |
// Update the frame. |
- [self setFrameSize:NSMakeSize(curX, curY)]; |
+ [self setFrameSize:NSMakeSize(ItemWidth(), curY)]; |
} |
return self; |
} |
@@ -89,53 +262,87 @@ NSSecureTextField* PasswordLabel(const base::string16& text) { |
@end |
+@interface ManagePasswordItemViewController () |
+- (void)onDeleteClicked:(id)sender; |
+- (void)onUndoClicked:(id)sender; |
+ |
+// Find the next content view and repaint. |
+- (void)refresh; |
+ |
+// Find the next content view. |
+- (void)updateContent; |
+ |
+// Repaint the content. |
+- (void)layoutContent; |
+@end |
+ |
@implementation ManagePasswordItemViewController |
- (id)initWithModel:(ManagePasswordsBubbleModel*)model |
- position:(password_manager::ui::PasswordItemPosition)position |
- minWidth:(CGFloat)minWidth { |
+ passwordForm:(const autofill::PasswordForm&)passwordForm |
+ position:(password_manager::ui::PasswordItemPosition)position { |
if ((self = [super initWithNibName:nil bundle:nil])) { |
model_ = model; |
position_ = position; |
- minWidth_ = minWidth; |
+ passwordForm_ = passwordForm; |
state_ = password_manager::ui::IsPendingState(model_->state()) |
? MANAGE_PASSWORD_ITEM_STATE_PENDING |
: MANAGE_PASSWORD_ITEM_STATE_MANAGE; |
- switch (state_) { |
- default: |
- NOTREACHED(); |
- case MANAGE_PASSWORD_ITEM_STATE_PENDING: |
- contentView_.reset([[ManagePasswordItemPendingView alloc] |
- initWithForm:model_->pending_credentials()]); |
- break; |
- case MANAGE_PASSWORD_ITEM_STATE_MANAGE: |
- NOTIMPLEMENTED(); |
- break; |
- case MANAGE_PASSWORD_ITEM_STATE_DELETED: |
- NOTIMPLEMENTED(); |
- break; |
- }; |
+ [self updateContent]; |
} |
return self; |
} |
-- (void)loadView { |
- self.view = [[[NSView alloc] initWithFrame:NSZeroRect] autorelease]; |
- [self.view addSubview:contentView_]; |
+- (void)onDeleteClicked:(id)sender { |
+ DCHECK_EQ(MANAGE_PASSWORD_ITEM_STATE_MANAGE, state_); |
+ state_ = MANAGE_PASSWORD_ITEM_STATE_DELETED; |
+ [self refresh]; |
+ model_->OnPasswordAction(passwordForm_, |
+ ManagePasswordsBubbleModel::REMOVE_PASSWORD); |
+} |
- // Update the view size according to the content view size, expanding if |
- // necessary to fill the min width. |
+- (void)onUndoClicked:(id)sender { |
+ DCHECK_EQ(MANAGE_PASSWORD_ITEM_STATE_DELETED, state_); |
+ state_ = MANAGE_PASSWORD_ITEM_STATE_MANAGE; |
+ [self refresh]; |
+ model_->OnPasswordAction(passwordForm_, |
+ ManagePasswordsBubbleModel::ADD_PASSWORD); |
+} |
+ |
+- (void)refresh { |
+ [self updateContent]; |
+ [self layoutContent]; |
+} |
+ |
+- (void)updateContent { |
+ switch (state_) { |
+ default: |
+ NOTREACHED(); |
+ case MANAGE_PASSWORD_ITEM_STATE_PENDING: |
+ contentView_.reset( |
+ [[ManagePasswordItemPendingView alloc] initWithForm:passwordForm_]); |
+ return; |
+ case MANAGE_PASSWORD_ITEM_STATE_MANAGE: |
+ contentView_.reset([[ManagePasswordItemManageView alloc] |
+ initWithForm:passwordForm_ |
+ target:self |
+ action:@selector(onDeleteClicked:)]); |
+ return; |
+ case MANAGE_PASSWORD_ITEM_STATE_DELETED: |
+ contentView_.reset([[ManagePasswordItemUndoView alloc] |
+ initWithTarget:self |
+ action:@selector(onUndoClicked:)]); |
+ return; |
+ }; |
+} |
+ |
+- (void)layoutContent { |
+ // Update the view size according to the content view size. |
const NSSize contentSize = [contentView_ frame].size; |
- const CGFloat width = |
- std::max(contentSize.width + 2 * kFramePadding, minWidth_); |
- const CGFloat height = |
- contentSize.height + 2 * views::kRelatedControlVerticalSpacing; |
- [self.view setFrameSize:NSMakeSize(width, height)]; |
+ [self.view setFrameSize:contentSize]; |
- // Position the content view with some padding in the center of the view. |
- [contentView_ |
- setFrameOrigin:NSMakePoint(kFramePadding, |
- views::kRelatedControlVerticalSpacing)]; |
+ // Add the content. |
+ [self.view setSubviews:@[ contentView_ ]]; |
// Add the borders, which go along the entire view. |
SkColor borderSkColor; |
@@ -153,18 +360,25 @@ NSSecureTextField* PasswordLabel(const base::string16& text) { |
if (position_ == password_manager::ui::FIRST_ITEM) { |
base::scoped_nsobject<CALayer> topBorder([[CALayer alloc] init]); |
[topBorder setBackgroundColor:borderColor]; |
- [topBorder |
- setFrame:CGRectMake(0, height - kBorderWidth, width, kBorderWidth)]; |
+ [topBorder setFrame:CGRectMake(0, |
+ contentSize.height - kBorderWidth, |
+ contentSize.width, |
+ kBorderWidth)]; |
[self.view.layer addSublayer:topBorder]; |
} |
// The bottom border is always present. |
base::scoped_nsobject<CALayer> bottomBorder([[CALayer alloc] init]); |
[bottomBorder setBackgroundColor:borderColor]; |
- [bottomBorder setFrame:CGRectMake(0, 0, width, kBorderWidth)]; |
+ [bottomBorder setFrame:CGRectMake(0, 0, contentSize.width, kBorderWidth)]; |
[self.view.layer addSublayer:bottomBorder]; |
} |
+- (void)loadView { |
+ self.view = [[[NSView alloc] initWithFrame:NSZeroRect] autorelease]; |
+ [self layoutContent]; |
+} |
+ |
@end |
@implementation ManagePasswordItemViewController (Testing) |
@@ -177,4 +391,42 @@ NSSecureTextField* PasswordLabel(const base::string16& text) { |
return contentView_.get(); |
} |
+- (autofill::PasswordForm)passwordForm { |
+ return passwordForm_; |
+} |
+ |
+@end |
+ |
+@implementation ManagePasswordItemClickableView |
+ |
+- (void)drawRect:(NSRect)dirtyRect { |
+ [super drawRect:dirtyRect]; |
+ if (hovering_) { |
+ [HoverColor() setFill]; |
+ NSRectFill(dirtyRect); |
+ } |
+} |
+ |
+- (void)mouseEntered:(NSEvent*)event { |
+ hovering_ = YES; |
+ [self setNeedsDisplay:YES]; |
+} |
+ |
+- (void)mouseExited:(NSEvent*)event { |
+ hovering_ = NO; |
+ [self setNeedsDisplay:YES]; |
+} |
+ |
+- (void)updateTrackingAreas { |
+ [super updateTrackingAreas]; |
+ if (trackingArea_.get()) |
+ [self removeTrackingArea:trackingArea_.get()]; |
+ NSTrackingAreaOptions options = NSTrackingMouseEnteredAndExited; |
+ trackingArea_.reset([[CrTrackingArea alloc] initWithRect:[self bounds] |
+ options:options |
+ owner:self |
+ userInfo:nil]); |
+ [self addTrackingArea:trackingArea_.get()]; |
+} |
+ |
@end |