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/autofill/password_generation_popup_view_cocoa.h"
9 #include "base/logging.h"
10 #include "base/strings/sys_string_conversions.h"
11 #include "chrome/browser/ui/autofill/autofill_popup_controller.h"
12 #include "chrome/browser/ui/autofill/autofill_popup_view.h"
13 #include "chrome/browser/ui/autofill/popup_constants.h"
14 #include "chrome/browser/ui/chrome_style.h"
15 #include "chrome/browser/ui/cocoa/autofill/password_generation_popup_view_bridge.h"
16 #import "chrome/browser/ui/cocoa/hyperlink_text_view.h"
17 #import "chrome/browser/ui/cocoa/l10n_util.h"
18 #include "components/autofill/core/browser/popup_item_ids.h"
19 #include "grit/theme_resources.h"
20 #include "skia/ext/skia_utils_mac.h"
21 #include "ui/base/resource/resource_bundle.h"
22 #include "ui/gfx/font_list.h"
23 #include "ui/gfx/image/image.h"
24 #include "ui/gfx/point.h"
25 #include "ui/gfx/range/range.h"
26 #include "ui/gfx/rect.h"
27 #include "ui/gfx/text_constants.h"
29 using autofill::AutofillPopupView;
30 using autofill::PasswordGenerationPopupController;
31 using autofill::PasswordGenerationPopupView;
32 using base::scoped_nsobject;
36 // The height of the divider between the password and help sections, in pixels.
37 const CGFloat kDividerHeight = 1;
39 // The amount of whitespace, in pixels, between lines of text in the password
41 const CGFloat kPasswordSectionVerticalSeparation = 5;
43 NSColor* DividerColor() {
44 return gfx::SkColorToCalibratedNSColor(
45 PasswordGenerationPopupView::kDividerColor);
48 NSColor* HelpTextBackgroundColor() {
49 return gfx::SkColorToCalibratedNSColor(
50 PasswordGenerationPopupView::kExplanatoryTextBackgroundColor);
53 NSColor* HelpTextColor() {
54 return gfx::SkColorToCalibratedNSColor(
55 PasswordGenerationPopupView::kExplanatoryTextColor);
58 NSColor* HelpLinkColor() {
59 return gfx::SkColorToCalibratedNSColor(chrome_style::GetLinkColor());
64 @implementation PasswordGenerationPopupViewCocoa
66 #pragma mark Initialisers
68 - (id)initWithFrame:(NSRect)frame {
73 - (id)initWithController:
74 (autofill::PasswordGenerationPopupController*)controller
76 if (self = [super initWithDelegate:controller frame:frame]) {
77 controller_ = controller;
79 passwordSection_.reset([[NSView alloc] initWithFrame:NSZeroRect]);
80 [self addSubview:passwordSection_];
83 [[self textFieldWithText:controller_->password()
84 attributes:[self passwordAttributes]] retain]);
85 [passwordSection_ addSubview:passwordField_];
87 passwordTitleField_.reset(
88 [[self textFieldWithText:controller_->SuggestedText()
89 attributes:[self passwordTitleAttributes]] retain]);
90 [passwordSection_ addSubview:passwordTitleField_];
92 keyIcon_.reset([[NSImageView alloc] initWithFrame:NSZeroRect]);
93 NSImage* keyImage = ResourceBundle::GetSharedInstance()
94 .GetImageNamed(IDR_GENERATE_PASSWORD_KEY)
96 [keyIcon_ setImage:keyImage];
97 [passwordSection_ addSubview:keyIcon_];
99 divider_.reset([[NSBox alloc] initWithFrame:NSZeroRect]);
100 [divider_ setBoxType:NSBoxCustom];
101 [divider_ setBorderType:NSLineBorder];
102 [divider_ setBorderColor:DividerColor()];
103 [self addSubview:divider_];
105 helpTextView_.reset([[HyperlinkTextView alloc] initWithFrame:NSZeroRect]);
106 [helpTextView_ setMessage:base::SysUTF16ToNSString(controller_->HelpText())
107 withFont:[self textFont]
108 messageColor:HelpTextColor()];
109 [helpTextView_ addLinkRange:controller_->HelpTextLinkRange().ToNSRange()
111 linkColor:HelpLinkColor()];
112 [helpTextView_ setDelegate:self];
113 [helpTextView_ setDrawsBackground:YES];
114 [helpTextView_ setBackgroundColor:HelpTextBackgroundColor()];
116 setTextContainerInset:NSMakeSize(controller_->kHorizontalPadding,
117 controller_->kHelpVerticalPadding)];
118 // Remove the underlining.
119 NSTextStorage* text = [helpTextView_ textStorage];
120 [text addAttribute:NSUnderlineStyleAttributeName
121 value:@(NSUnderlineStyleNone)
122 range:controller_->HelpTextLinkRange().ToNSRange()];
123 [self addSubview:helpTextView_];
129 #pragma mark NSView implementation:
131 - (void)drawRect:(NSRect)dirtyRect {
132 [super drawRect:dirtyRect];
134 // If the view is in the process of being destroyed, don't bother drawing.
138 [self drawBackgroundAndBorder];
140 if (controller_->password_selected()) {
141 // Draw a highlight under the suggested password.
142 NSRect highlightBounds = [passwordSection_ frame];
143 [[self highlightColor] set];
144 [NSBezierPath fillRect:highlightBounds];
148 #pragma mark Public API:
150 - (NSSize)preferredSize {
151 const NSSize passwordTitleSize =
152 [base::SysUTF16ToNSString(controller_->SuggestedText())
153 sizeWithAttributes:@{ NSFontAttributeName : [self boldFont] }];
154 const NSSize passwordSize = [base::SysUTF16ToNSString(controller_->password())
155 sizeWithAttributes:@{ NSFontAttributeName : [self textFont] }];
158 autofill::kPopupBorderThickness +
159 controller_->kHorizontalPadding +
160 [[keyIcon_ image] size].width +
161 controller_->kHorizontalPadding +
162 std::max(passwordSize.width, passwordTitleSize.width) +
163 controller_->kHorizontalPadding +
164 autofill::kPopupBorderThickness;
166 width = std::max(width, (CGFloat)controller_->GetMinimumWidth());
169 autofill::kPopupBorderThickness +
170 controller_->kHelpVerticalPadding +
171 [self helpSizeForPopupWidth:width].height +
172 controller_->kHelpVerticalPadding +
173 autofill::kPopupBorderThickness;
175 if (controller_->display_password())
176 height += controller_->kPopupPasswordSectionHeight;
178 return NSMakeSize(width, height);
181 - (void)updateBoundsAndRedrawPopup {
182 const CGFloat popupWidth = controller_->popup_bounds().width();
183 const CGFloat contentWidth =
184 popupWidth - (2 * autofill::kPopupBorderThickness);
185 const CGFloat contentHeight = controller_->popup_bounds().height() -
186 (2 * autofill::kPopupBorderThickness);
188 if (controller_->display_password()) {
189 // The password can change while the bubble is shown: If the user has
190 // accepted the password and then selects the form again and starts deleting
191 // the password, the field will be initially invisible and then become
193 [self updatePassword];
195 // Lay out the password section, which includes the key icon, the title, and
196 // the suggested password.
198 setFrame:NSMakeRect(autofill::kPopupBorderThickness,
199 autofill::kPopupBorderThickness,
201 controller_->kPopupPasswordSectionHeight)];
203 // The key icon falls to the left of the title and password.
204 const NSSize imageSize = [[keyIcon_ image] size];
205 const CGFloat keyX = controller_->kHorizontalPadding;
207 std::ceil((controller_->kPopupPasswordSectionHeight / 2.0) -
208 (imageSize.height / 2.0));
209 [keyIcon_ setFrame:{ NSMakePoint(keyX, keyY), imageSize }];
211 // The title and password fall to the right of the key icon and are centered
212 // vertically as a group with some padding in between.
213 [passwordTitleField_ sizeToFit];
214 [passwordField_ sizeToFit];
215 const CGFloat groupHeight = NSHeight([passwordField_ frame]) +
216 kPasswordSectionVerticalSeparation +
217 NSHeight([passwordTitleField_ frame]);
218 const CGFloat groupX =
219 NSMaxX([keyIcon_ frame]) + controller_->kHorizontalPadding;
220 const CGFloat groupY =
221 std::ceil((controller_->kPopupPasswordSectionHeight / 2.0) -
222 (groupHeight / 2.0));
223 [passwordField_ setFrameOrigin:NSMakePoint(groupX, groupY)];
224 const CGFloat titleY = groupY +
225 NSHeight([passwordField_ frame]) +
226 kPasswordSectionVerticalSeparation;
227 [passwordTitleField_ setFrameOrigin:NSMakePoint(groupX, titleY)];
229 // Layout the divider, which falls immediately below the password section.
230 const CGFloat dividerX = autofill::kPopupBorderThickness;
231 const CGFloat dividerY = NSMaxY([passwordSection_ frame]);
232 NSRect dividerFrame =
233 NSMakeRect(dividerX, dividerY, contentWidth, kDividerHeight);
234 [divider_ setFrame:dividerFrame];
237 // Layout the help section beneath the divider (if applicable, otherwise
238 // beneath the border).
239 const CGFloat helpX = autofill::kPopupBorderThickness;
240 const CGFloat helpY = controller_->display_password()
241 ? NSMaxY([divider_ frame])
242 : autofill::kPopupBorderThickness;
243 const CGFloat helpHeight = contentHeight -
244 NSHeight([passwordSection_ frame]) -
245 NSHeight([divider_ frame]);
246 [helpTextView_ setFrame:NSMakeRect(helpX, helpY, contentWidth, helpHeight)];
248 [super updateBoundsAndRedrawPopup];
251 - (BOOL)isPointInPasswordBounds:(NSPoint)point {
252 return NSPointInRect(point, [passwordSection_ frame]);
255 - (void)controllerDestroyed {
257 [super delegateDestroyed];
260 #pragma mark NSTextViewDelegate implementation:
262 - (BOOL)textView:(NSTextView*)textView
263 clickedOnLink:(id)link
264 atIndex:(NSUInteger)charIndex {
265 controller_->OnSavedPasswordsLinkClicked();
269 #pragma mark Private helpers:
271 - (void)updatePassword {
272 base::scoped_nsobject<NSMutableAttributedString> updatedPassword(
273 [[NSMutableAttributedString alloc]
274 initWithString:base::SysUTF16ToNSString(controller_->password())
275 attributes:[self passwordAttributes]]);
276 [passwordField_ setAttributedStringValue:updatedPassword];
279 - (NSDictionary*)passwordTitleAttributes {
280 scoped_nsobject<NSMutableParagraphStyle> paragraphStyle(
281 [[NSMutableParagraphStyle alloc] init]);
282 [paragraphStyle setAlignment:NSLeftTextAlignment];
284 NSFontAttributeName : [self boldFont],
285 NSForegroundColorAttributeName : [self nameColor],
286 NSParagraphStyleAttributeName : paragraphStyle.autorelease()
290 - (NSDictionary*)passwordAttributes {
291 scoped_nsobject<NSMutableParagraphStyle> paragraphStyle(
292 [[NSMutableParagraphStyle alloc] init]);
293 [paragraphStyle setAlignment:NSLeftTextAlignment];
295 NSFontAttributeName : [self textFont],
296 NSForegroundColorAttributeName : [self nameColor],
297 NSParagraphStyleAttributeName : paragraphStyle.autorelease()
301 - (NSTextField*)textFieldWithText:(const base::string16&)text
302 attributes:(NSDictionary*)attributes {
303 NSTextField* textField =
304 [[[NSTextField alloc] initWithFrame:NSZeroRect] autorelease];
305 scoped_nsobject<NSAttributedString> attributedString(
306 [[NSAttributedString alloc]
307 initWithString:base::SysUTF16ToNSString(text)
308 attributes:attributes]);
309 [textField setAttributedStringValue:attributedString.autorelease()];
310 [textField setEditable:NO];
311 [textField setSelectable:NO];
312 [textField setDrawsBackground:NO];
313 [textField setBezeled:NO];
317 - (NSSize)helpSizeForPopupWidth:(CGFloat)width {
318 const CGFloat helpWidth = width -
319 2 * controller_->kHorizontalPadding -
320 2 * autofill::kPopupBorderThickness;
321 const NSSize size = NSMakeSize(helpWidth, MAXFLOAT);
322 NSRect textFrame = [base::SysUTF16ToNSString(controller_->HelpText())
323 boundingRectWithSize:size
324 options:NSLineBreakByWordWrapping |
325 NSStringDrawingUsesLineFragmentOrigin
326 attributes:@{ NSFontAttributeName : [self textFont] }];
327 return textFrame.size;
330 - (NSFont*)boldFont {
331 return [NSFont boldSystemFontOfSize:[NSFont smallSystemFontSize]];
334 - (NSFont*)textFont {
335 return [NSFont systemFontOfSize:[NSFont smallSystemFontSize]];