#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"
-#include "ui/native_theme/native_theme.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 {
-// TODO(dconnelly): Figure out how to share all these constants.
-static const CGFloat kBorderWidth = 1;
-static const CGFloat kFramePadding = 16;
+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)];
[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]);
[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;
}
@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);
+}
+
+- (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);
+}
- // Update the view size according to the content view size, expanding if
- // necessary to fill the min width.
+- (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.
- CGColorRef borderColor =
- gfx::CGColorCreateFromSkColor(ui::NativeTheme::instance()->GetSystemColor(
- ui::NativeTheme::kColorId_EnabledMenuButtonBorderColor));
+ SkColor borderSkColor;
+ ui::CommonThemeGetSystemColor(
+ ui::NativeTheme::kColorId_EnabledMenuButtonBorderColor, &borderSkColor);
+ CGColorRef borderColor = gfx::CGColorCreateFromSkColor(borderSkColor);
// Mac views don't have backing layers by default.
base::scoped_nsobject<CALayer> rootLayer([[CALayer alloc] init]);
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)
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 | NSTrackingActiveInKeyWindow;
+ trackingArea_.reset([[CrTrackingArea alloc] initWithRect:[self bounds]
+ options:options
+ owner:self
+ userInfo:nil]);
+ [self addTrackingArea:trackingArea_.get()];
+}
+
@end