BookmarkManager: Fix 'new folder text field size changes on clicking it' issue.
[chromium-blink-merge.git] / chrome / browser / ui / cocoa / autofill / password_generation_popup_view_cocoa.mm
blob0e1f46b073da441a90d42cc546f4b345fd3f5aed
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"
7 #include <cmath>
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/l10n_util.h"
17 #include "components/autofill/core/browser/popup_item_ids.h"
18 #include "grit/theme_resources.h"
19 #include "skia/ext/skia_utils_mac.h"
20 #import "ui/base/cocoa/controls/hyperlink_text_view.h"
21 #include "ui/base/resource/resource_bundle.h"
22 #include "ui/gfx/font_list.h"
23 #include "ui/gfx/geometry/point.h"
24 #include "ui/gfx/geometry/rect.h"
25 #include "ui/gfx/image/image.h"
26 #include "ui/gfx/range/range.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;
34 namespace {
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
40 // section.
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());
62 }  // namespace
64 @implementation PasswordGenerationPopupViewCocoa
66 #pragma mark Initialisers
68 - (id)initWithFrame:(NSRect)frame {
69   NOTREACHED();
70   return nil;
73 - (id)initWithController:
74     (autofill::PasswordGenerationPopupController*)controller
75                    frame:(NSRect)frame {
76   if (self = [super initWithDelegate:controller frame:frame]) {
77     controller_ = controller;
79     passwordSection_.reset([[NSView alloc] initWithFrame:NSZeroRect]);
80     [self addSubview:passwordSection_];
82     passwordField_.reset(
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)
95         .ToNSImage();
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()
110                        withName:@""
111                       linkColor:HelpLinkColor()];
112     [helpTextView_ setDelegate:self];
113     [helpTextView_ setDrawsBackground:YES];
114     [helpTextView_ setBackgroundColor:HelpTextBackgroundColor()];
115     [helpTextView_
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_];
126   return self;
129 - (void)updateTrackingAreas {
130   [super updateTrackingAreas];
131   if (helpTextTrackingArea_.get())
132     [self removeTrackingArea:helpTextTrackingArea_.get()];
134   // Set up tracking for the help text so the cursor, etc. is properly handled.
135   // Must set tracking to "always" because the autofill window is never key.
136   NSTrackingAreaOptions options = NSTrackingActiveAlways |
137                                   NSTrackingMouseEnteredAndExited |
138                                   NSTrackingMouseMoved |
139                                   NSTrackingCursorUpdate;
140   helpTextTrackingArea_.reset(
141       [[CrTrackingArea alloc] initWithRect:[self bounds]
142                                    options:options
143                                      owner:helpTextView_.get()
144                                   userInfo:nil]);
146   [self addTrackingArea:helpTextTrackingArea_.get()];
149 #pragma mark NSView implementation:
151 - (void)drawRect:(NSRect)dirtyRect {
152   [super drawRect:dirtyRect];
154   // If the view is in the process of being destroyed, don't bother drawing.
155   if (!controller_)
156     return;
158   [self drawBackgroundAndBorder];
160   if (controller_->password_selected()) {
161     // Draw a highlight under the suggested password.
162     NSRect highlightBounds = [passwordSection_ frame];
163     [[self highlightColor] set];
164     [NSBezierPath fillRect:highlightBounds];
165   }
168 #pragma mark Public API:
170 - (NSSize)preferredSize {
171   const NSSize passwordTitleSize =
172       [base::SysUTF16ToNSString(controller_->SuggestedText())
173           sizeWithAttributes:@{ NSFontAttributeName : [self boldFont] }];
174   const NSSize passwordSize = [base::SysUTF16ToNSString(controller_->password())
175       sizeWithAttributes:@{ NSFontAttributeName : [self textFont] }];
177   CGFloat width =
178       autofill::kPopupBorderThickness +
179       controller_->kHorizontalPadding +
180       [[keyIcon_ image] size].width +
181       controller_->kHorizontalPadding +
182       std::max(passwordSize.width, passwordTitleSize.width) +
183       controller_->kHorizontalPadding +
184       autofill::kPopupBorderThickness;
186   width = std::max(width, (CGFloat)controller_->GetMinimumWidth());
187   CGFloat contentWidth = width - (2 * controller_->kHorizontalPadding);
189   CGFloat height =
190       autofill::kPopupBorderThickness +
191       controller_->kHelpVerticalPadding +
192       [self helpSizeForPopupWidth:contentWidth].height +
193       controller_->kHelpVerticalPadding +
194       autofill::kPopupBorderThickness;
196   if (controller_->display_password())
197     height += controller_->kPopupPasswordSectionHeight;
199   return NSMakeSize(width, height);
202 - (void)updateBoundsAndRedrawPopup {
203   const CGFloat popupWidth = controller_->popup_bounds().width();
204   const CGFloat contentWidth =
205       popupWidth - (2 * autofill::kPopupBorderThickness);
206   const CGFloat contentHeight = controller_->popup_bounds().height() -
207                                 (2 * autofill::kPopupBorderThickness);
209   if (controller_->display_password()) {
210     // The password can change while the bubble is shown: If the user has
211     // accepted the password and then selects the form again and starts deleting
212     // the password, the field will be initially invisible and then become
213     // visible.
214     [self updatePassword];
216     // Lay out the password section, which includes the key icon, the title, and
217     // the suggested password.
218     [passwordSection_
219         setFrame:NSMakeRect(autofill::kPopupBorderThickness,
220                             autofill::kPopupBorderThickness,
221                             contentWidth,
222                             controller_->kPopupPasswordSectionHeight)];
224     // The key icon falls to the left of the title and password.
225     const NSSize imageSize = [[keyIcon_ image] size];
226     const CGFloat keyX = controller_->kHorizontalPadding;
227     const CGFloat keyY =
228         std::ceil((controller_->kPopupPasswordSectionHeight / 2.0) -
229                   (imageSize.height / 2.0));
230     [keyIcon_ setFrame:{ NSMakePoint(keyX, keyY), imageSize }];
232     // The title and password fall to the right of the key icon and are centered
233     // vertically as a group with some padding in between.
234     [passwordTitleField_ sizeToFit];
235     [passwordField_ sizeToFit];
236     const CGFloat groupHeight = NSHeight([passwordField_ frame]) +
237                                 kPasswordSectionVerticalSeparation +
238                                 NSHeight([passwordTitleField_ frame]);
239     const CGFloat groupX =
240         NSMaxX([keyIcon_ frame]) + controller_->kHorizontalPadding;
241     const CGFloat groupY =
242         std::ceil((controller_->kPopupPasswordSectionHeight / 2.0) -
243                   (groupHeight / 2.0));
244     [passwordField_ setFrameOrigin:NSMakePoint(groupX, groupY)];
245     const CGFloat titleY = groupY +
246                            NSHeight([passwordField_ frame]) +
247                            kPasswordSectionVerticalSeparation;
248     [passwordTitleField_ setFrameOrigin:NSMakePoint(groupX, titleY)];
250     // Layout the divider, which falls immediately below the password section.
251     const CGFloat dividerX = autofill::kPopupBorderThickness;
252     const CGFloat dividerY = NSMaxY([passwordSection_ frame]);
253     NSRect dividerFrame =
254         NSMakeRect(dividerX, dividerY, contentWidth, kDividerHeight);
255     [divider_ setFrame:dividerFrame];
256   }
258   // Layout the help section beneath the divider (if applicable, otherwise
259   // beneath the border).
260   const CGFloat helpX = autofill::kPopupBorderThickness;
261   const CGFloat helpY = controller_->display_password()
262       ? NSMaxY([divider_ frame])
263       : autofill::kPopupBorderThickness;
264   const CGFloat helpHeight = contentHeight -
265                              NSHeight([passwordSection_ frame]) -
266                              NSHeight([divider_ frame]);
267   [helpTextView_ setFrame:NSMakeRect(helpX, helpY, contentWidth, helpHeight)];
269   [super updateBoundsAndRedrawPopup];
272 - (BOOL)isPointInPasswordBounds:(NSPoint)point {
273   return NSPointInRect(point, [passwordSection_ frame]);
276 - (void)controllerDestroyed {
277   controller_ = NULL;
278   [super delegateDestroyed];
281 #pragma mark NSTextViewDelegate implementation:
283 - (BOOL)textView:(NSTextView*)textView
284    clickedOnLink:(id)link
285          atIndex:(NSUInteger)charIndex {
286   controller_->OnSavedPasswordsLinkClicked();
287   return YES;
290 #pragma mark Private helpers:
292 - (void)updatePassword {
293   base::scoped_nsobject<NSMutableAttributedString> updatedPassword(
294       [[NSMutableAttributedString alloc]
295           initWithString:base::SysUTF16ToNSString(controller_->password())
296               attributes:[self passwordAttributes]]);
297   [passwordField_ setAttributedStringValue:updatedPassword];
300 - (NSDictionary*)passwordTitleAttributes {
301   scoped_nsobject<NSMutableParagraphStyle> paragraphStyle(
302       [[NSMutableParagraphStyle alloc] init]);
303   [paragraphStyle setAlignment:NSLeftTextAlignment];
304   return @{
305     NSFontAttributeName : [self boldFont],
306     NSForegroundColorAttributeName : [self nameColor],
307     NSParagraphStyleAttributeName : paragraphStyle.autorelease()
308   };
311 - (NSDictionary*)passwordAttributes {
312   scoped_nsobject<NSMutableParagraphStyle> paragraphStyle(
313       [[NSMutableParagraphStyle alloc] init]);
314   [paragraphStyle setAlignment:NSLeftTextAlignment];
315   return @{
316     NSFontAttributeName : [self textFont],
317     NSForegroundColorAttributeName : [self nameColor],
318     NSParagraphStyleAttributeName : paragraphStyle.autorelease()
319   };
322 - (NSTextField*)textFieldWithText:(const base::string16&)text
323                        attributes:(NSDictionary*)attributes {
324   NSTextField* textField =
325       [[[NSTextField alloc] initWithFrame:NSZeroRect] autorelease];
326   scoped_nsobject<NSAttributedString> attributedString(
327       [[NSAttributedString alloc]
328           initWithString:base::SysUTF16ToNSString(text)
329               attributes:attributes]);
330   [textField setAttributedStringValue:attributedString.autorelease()];
331   [textField setEditable:NO];
332   [textField setSelectable:NO];
333   [textField setDrawsBackground:NO];
334   [textField setBezeled:NO];
335   return textField;
338 - (NSSize)helpSizeForPopupWidth:(CGFloat)width {
339   const CGFloat helpWidth = width -
340                             2 * controller_->kHorizontalPadding -
341                             2 * autofill::kPopupBorderThickness;
342   const NSSize size = NSMakeSize(helpWidth, MAXFLOAT);
343   NSRect textFrame = [base::SysUTF16ToNSString(controller_->HelpText())
344       boundingRectWithSize:size
345                    options:NSLineBreakByWordWrapping |
346                            NSStringDrawingUsesLineFragmentOrigin
347                 attributes:@{ NSFontAttributeName : [self textFont] }];
348   return textFrame.size;
351 - (NSFont*)boldFont {
352   return [NSFont boldSystemFontOfSize:[NSFont smallSystemFontSize]];
355 - (NSFont*)textFont {
356   return [NSFont systemFontOfSize:[NSFont smallSystemFontSize]];
359 @end