1 // Copyright 2014 The Chromium Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
5 #import "chrome/browser/ui/cocoa/passwords/manage_password_item_view_controller.h"
7 #include "base/logging.h"
8 #include "base/strings/string16.h"
9 #include "base/strings/sys_string_conversions.h"
10 #include "chrome/browser/ui/chrome_style.h"
11 #import "chrome/browser/ui/cocoa/passwords/manage_passwords_bubble_content_view_controller.h"
12 #include "chrome/browser/ui/passwords/manage_passwords_bubble_model.h"
13 #include "grit/generated_resources.h"
14 #include "skia/ext/skia_utils_mac.h"
15 #import "ui/base/cocoa/controls/hyperlink_button_cell.h"
16 #import "ui/base/cocoa/hover_image_button.h"
17 #include "ui/base/l10n/l10n_util.h"
18 #include "ui/gfx/image/image.h"
19 #include "ui/native_theme/common_theme.h"
20 #include "ui/resources/grit/ui_resources.h"
21 #include "ui/views/layout/layout_constants.h"
23 using namespace password_manager::mac::ui;
27 const CGFloat kBorderWidth = 1;
28 const SkColor kHoverColor = SkColorSetARGBInline(0xFF, 0xEB, 0xEB, 0xEB);
30 NSColor* HoverColor() {
31 return gfx::SkColorToCalibratedNSColor(kHoverColor);
35 return [NSFont systemFontOfSize:[NSFont smallSystemFontSize]];
38 NSSize LabelSize(int resourceID) {
39 return [l10n_util::GetNSString(resourceID)
40 sizeWithAttributes:@{NSFontAttributeName : LabelFont()}];
43 CGFloat FirstFieldWidth() {
44 const CGFloat undoExplanationWidth =
45 LabelSize(IDS_MANAGE_PASSWORDS_DELETED).width;
46 const CGFloat kUsernameWidth =
47 ManagePasswordsBubbleModel::UsernameFieldWidth();
48 const CGFloat width = std::max(kUsernameWidth, undoExplanationWidth);
52 CGFloat SecondFieldWidth() {
53 const CGFloat undoLinkWidth =
54 LabelSize(IDS_MANAGE_PASSWORDS_UNDO).width;
55 const CGFloat kPasswordWidth =
56 ManagePasswordsBubbleModel::PasswordFieldWidth();
57 const CGFloat width = std::max(kPasswordWidth, undoLinkWidth);
65 views::kItemLabelSpacing +
67 views::kItemLabelSpacing +
68 chrome_style::GetCloseButtonSize() +
73 void InitLabel(NSTextField* textField, const base::string16& text) {
74 [textField setStringValue:base::SysUTF16ToNSString(text)];
75 [textField setEditable:NO];
76 [textField setSelectable:NO];
77 [textField setDrawsBackground:NO];
78 [textField setBezeled:NO];
79 [textField setFont:LabelFont()];
80 [textField sizeToFit];
83 NSTextField* Label(const base::string16& text) {
84 base::scoped_nsobject<NSTextField> textField(
85 [[NSTextField alloc] initWithFrame:NSZeroRect]);
86 InitLabel(textField, text);
87 return textField.autorelease();
90 NSTextField* UsernameLabel(const base::string16& text) {
91 NSTextField* textField = Label(text);
93 setFrameSize:NSMakeSize(FirstFieldWidth(), NSHeight([textField frame]))];
97 NSSecureTextField* PasswordLabel(const base::string16& text) {
98 base::scoped_nsobject<NSSecureTextField> textField(
99 [[NSSecureTextField alloc] initWithFrame:NSZeroRect]);
100 InitLabel(textField, text);
102 setFrameSize:NSMakeSize(SecondFieldWidth(), NSHeight([textField frame]))];
103 return textField.autorelease();
106 base::string16 GetDisplayUsername(const autofill::PasswordForm& form) {
107 return form.username_value.empty() ?
108 l10n_util::GetStringUTF16(IDS_PASSWORD_MANAGER_EMPTY_LOGIN) :
114 @implementation ManagePasswordItemUndoView
115 - (id)initWithTarget:(id)target action:(SEL)action {
116 if ((self = [super init])) {
117 // The button should look like a link.
118 undoButton_.reset([[NSButton alloc] initWithFrame:NSZeroRect]);
119 base::scoped_nsobject<HyperlinkButtonCell> cell([[HyperlinkButtonCell alloc]
120 initTextCell:l10n_util::GetNSString(IDS_MANAGE_PASSWORDS_UNDO)]);
121 [cell setControlSize:NSSmallControlSize];
122 [cell setShouldUnderline:NO];
123 [cell setUnderlineOnHover:NO];
124 [cell setTextColor:gfx::SkColorToCalibratedNSColor(
125 chrome_style::GetLinkColor())];
126 [undoButton_ setCell:cell.get()];
127 [undoButton_ sizeToFit];
128 [undoButton_ setTarget:target];
129 [undoButton_ setAction:action];
131 const CGFloat width = ItemWidth();
132 CGFloat curX = kFramePadding;
133 CGFloat curY = views::kRelatedControlVerticalSpacing;
135 // Add the explanation text.
137 Label(l10n_util::GetStringUTF16(IDS_MANAGE_PASSWORDS_DELETED));
138 [label setFrameOrigin:NSMakePoint(curX, curY)];
139 [self addSubview:label];
141 // The undo button should be right-aligned.
142 curX = width - kFramePadding - NSWidth([undoButton_ frame]);
143 [undoButton_ setFrameOrigin:NSMakePoint(curX, curY)];
144 [self addSubview:undoButton_ ];
146 // Move to the top-right of the delete button.
147 curX = NSMaxX([undoButton_ frame]) + kFramePadding;
148 curY = NSMaxY([undoButton_ frame]) + views::kRelatedControlVerticalSpacing;
151 DCHECK_EQ(width, curX);
152 [self setFrameSize:NSMakeSize(curX, curY)];
158 @implementation ManagePasswordItemUndoView (Testing)
159 - (NSButton*)undoButton {
160 return undoButton_.get();
164 @implementation ManagePasswordItemManageView
165 - (id)initWithForm:(const autofill::PasswordForm&)form
168 if ((self = [super init])) {
169 deleteButton_.reset([[HoverImageButton alloc] initWithFrame:NSZeroRect]);
170 [deleteButton_ setFrameSize:NSMakeSize(chrome_style::GetCloseButtonSize(),
171 chrome_style::GetCloseButtonSize())];
172 [deleteButton_ setBordered:NO];
173 [[deleteButton_ cell] setHighlightsBy:NSNoCellMask];
174 ui::ResourceBundle& bundle = ui::ResourceBundle::GetSharedInstance();
176 setDefaultImage:bundle.GetImageNamed(IDR_CLOSE_2).ToNSImage()];
178 setHoverImage:bundle.GetImageNamed(IDR_CLOSE_2_H).ToNSImage()];
180 setPressedImage:bundle.GetImageNamed(IDR_CLOSE_2_P).ToNSImage()];
181 [deleteButton_ setTarget:target];
182 [deleteButton_ setAction:action];
184 const CGFloat width = ItemWidth();
185 CGFloat curX = kFramePadding;
186 CGFloat curY = views::kRelatedControlVerticalSpacing;
189 usernameField_.reset([UsernameLabel(GetDisplayUsername(form)) retain]);
190 [usernameField_ setFrameOrigin:NSMakePoint(curX, curY)];
191 [self addSubview:usernameField_];
193 // Move to the right of the username and add the password.
194 curX = NSMaxX([usernameField_ frame]) + views::kItemLabelSpacing;
195 passwordField_.reset([PasswordLabel(form.password_value) retain]);
196 [passwordField_ setFrameOrigin:NSMakePoint(curX, curY)];
197 [self addSubview:passwordField_];
199 // The delete button should be right-aligned.
200 curX = width - kFramePadding - NSWidth([deleteButton_ frame]);
201 [deleteButton_ setFrameOrigin:NSMakePoint(curX, curY)];
202 [self addSubview:deleteButton_];
204 // Move to the top-right of the delete button.
205 curX = NSMaxX([deleteButton_ frame]) + kFramePadding;
207 NSMaxY([deleteButton_ frame]) + views::kRelatedControlVerticalSpacing;
210 DCHECK_EQ(width, curX);
211 [self setFrameSize:NSMakeSize(curX, curY)];
217 @implementation ManagePasswordItemManageView (Testing)
218 - (NSTextField*)usernameField {
219 return usernameField_.get();
221 - (NSSecureTextField*)passwordField {
222 return passwordField_.get();
224 - (NSButton*)deleteButton {
225 return deleteButton_.get();
229 @implementation ManagePasswordItemPendingView
231 - (id)initWithForm:(const autofill::PasswordForm&)form {
232 if ((self = [super initWithFrame:NSZeroRect])) {
233 CGFloat curX = kFramePadding;
234 CGFloat curY = views::kRelatedControlVerticalSpacing;
237 usernameField_.reset([UsernameLabel(GetDisplayUsername(form)) retain]);
238 [usernameField_ setFrameOrigin:NSMakePoint(curX, curY)];
239 [self addSubview:usernameField_];
241 // Move to the right of the username and add the password.
242 curX = NSMaxX([usernameField_ frame]) + views::kItemLabelSpacing;
243 passwordField_.reset([PasswordLabel(form.password_value) retain]);
244 [passwordField_ setFrameOrigin:NSMakePoint(curX, curY)];
245 [self addSubview:passwordField_];
247 // Move to the top-right of the password.
249 NSMaxY([passwordField_ frame]) + views::kRelatedControlVerticalSpacing;
252 [self setFrameSize:NSMakeSize(ItemWidth(), curY)];
259 @implementation ManagePasswordItemPendingView (Testing)
261 - (NSTextField*)usernameField {
262 return usernameField_.get();
265 - (NSSecureTextField*)passwordField {
266 return passwordField_.get();
271 @interface ManagePasswordItemViewController ()
272 - (void)onDeleteClicked:(id)sender;
273 - (void)onUndoClicked:(id)sender;
275 // Find the next content view and repaint.
278 // Find the next content view.
279 - (void)updateContent;
281 // Repaint the content.
282 - (void)layoutContent;
285 @implementation ManagePasswordItemViewController
287 - (id)initWithModel:(ManagePasswordsBubbleModel*)model
288 passwordForm:(const autofill::PasswordForm&)passwordForm
289 position:(password_manager::ui::PasswordItemPosition)position {
290 if ((self = [super initWithNibName:nil bundle:nil])) {
292 position_ = position;
293 passwordForm_ = passwordForm;
294 state_ = password_manager::ui::IsPendingState(model_->state())
295 ? MANAGE_PASSWORD_ITEM_STATE_PENDING
296 : MANAGE_PASSWORD_ITEM_STATE_MANAGE;
297 [self updateContent];
302 - (void)onDeleteClicked:(id)sender {
303 DCHECK_EQ(MANAGE_PASSWORD_ITEM_STATE_MANAGE, state_);
304 state_ = MANAGE_PASSWORD_ITEM_STATE_DELETED;
306 model_->OnPasswordAction(passwordForm_,
307 ManagePasswordsBubbleModel::REMOVE_PASSWORD);
310 - (void)onUndoClicked:(id)sender {
311 DCHECK_EQ(MANAGE_PASSWORD_ITEM_STATE_DELETED, state_);
312 state_ = MANAGE_PASSWORD_ITEM_STATE_MANAGE;
314 model_->OnPasswordAction(passwordForm_,
315 ManagePasswordsBubbleModel::ADD_PASSWORD);
319 [self updateContent];
320 [self layoutContent];
323 - (void)updateContent {
327 case MANAGE_PASSWORD_ITEM_STATE_PENDING:
329 [[ManagePasswordItemPendingView alloc] initWithForm:passwordForm_]);
331 case MANAGE_PASSWORD_ITEM_STATE_MANAGE:
332 contentView_.reset([[ManagePasswordItemManageView alloc]
333 initWithForm:passwordForm_
335 action:@selector(onDeleteClicked:)]);
337 case MANAGE_PASSWORD_ITEM_STATE_DELETED:
338 contentView_.reset([[ManagePasswordItemUndoView alloc]
340 action:@selector(onUndoClicked:)]);
345 - (void)layoutContent {
346 // Update the view size according to the content view size.
347 const NSSize contentSize = [contentView_ frame].size;
348 [self.view setFrameSize:contentSize];
351 [self.view setSubviews:@[ contentView_ ]];
353 // Add the borders, which go along the entire view.
354 SkColor borderSkColor;
355 ui::CommonThemeGetSystemColor(
356 ui::NativeTheme::kColorId_EnabledMenuButtonBorderColor, &borderSkColor);
357 CGColorRef borderColor = gfx::CGColorCreateFromSkColor(borderSkColor);
359 // Mac views don't have backing layers by default.
360 base::scoped_nsobject<CALayer> rootLayer([[CALayer alloc] init]);
361 [rootLayer setFrame:NSRectToCGRect(self.view.frame)];
362 [self.view setLayer:rootLayer];
363 [self.view setWantsLayer:YES];
365 // The top border is only present for the first item.
366 if (position_ == password_manager::ui::FIRST_ITEM) {
367 base::scoped_nsobject<CALayer> topBorder([[CALayer alloc] init]);
368 [topBorder setBackgroundColor:borderColor];
369 [topBorder setFrame:CGRectMake(0,
370 contentSize.height - kBorderWidth,
373 [self.view.layer addSublayer:topBorder];
376 // The bottom border is always present.
377 base::scoped_nsobject<CALayer> bottomBorder([[CALayer alloc] init]);
378 [bottomBorder setBackgroundColor:borderColor];
379 [bottomBorder setFrame:CGRectMake(0, 0, contentSize.width, kBorderWidth)];
380 [self.view.layer addSublayer:bottomBorder];
384 self.view = [[[NSView alloc] initWithFrame:NSZeroRect] autorelease];
385 [self layoutContent];
390 @implementation ManagePasswordItemViewController (Testing)
392 - (ManagePasswordItemState)state {
396 - (NSView*)contentView {
397 return contentView_.get();
400 - (autofill::PasswordForm)passwordForm {
401 return passwordForm_;
406 @implementation ManagePasswordItemClickableView
408 - (void)drawRect:(NSRect)dirtyRect {
409 [super drawRect:dirtyRect];
411 [HoverColor() setFill];
412 NSRectFill(dirtyRect);
416 - (void)mouseEntered:(NSEvent*)event {
418 [self setNeedsDisplay:YES];
421 - (void)mouseExited:(NSEvent*)event {
423 [self setNeedsDisplay:YES];
426 - (void)updateTrackingAreas {
427 [super updateTrackingAreas];
428 if (trackingArea_.get())
429 [self removeTrackingArea:trackingArea_.get()];
430 NSTrackingAreaOptions options =
431 NSTrackingMouseEnteredAndExited | NSTrackingActiveInKeyWindow;
432 trackingArea_.reset([[CrTrackingArea alloc] initWithRect:[self bounds]
436 [self addTrackingArea:trackingArea_.get()];