Disable view source for Developer Tools.
[chromium-blink-merge.git] / chrome / browser / ui / cocoa / autofill / autofill_suggestion_container.mm
blobfa4d39a1e88dc7ba0b54ed9bd7b17f084465d987
1 // Copyright 2013 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/autofill_suggestion_container.h"
7 #include <algorithm>
8 #include <cmath>
10 #include "base/logging.h"
11 #include "base/mac/scoped_nsobject.h"
12 #include "base/strings/sys_string_conversions.h"
13 #include "chrome/browser/ui/autofill/autofill_dialog_view_delegate.h"
14 #include "chrome/browser/ui/chrome_style.h"
15 #include "chrome/browser/ui/cocoa/autofill/autofill_dialog_constants.h"
16 #import "chrome/browser/ui/cocoa/autofill/autofill_textfield.h"
17 #include "skia/ext/skia_utils_mac.h"
19 namespace {
21 // Horizontal padding between text and other elements (in pixels).
22 const int kAroundTextPadding = 4;
24 // Padding at the top of suggestions.
25 const CGFloat kTopPadding = 10;
27 // Indicates infinite size in either vertical or horizontal direction.
28 // Technically, CGFLOAT_MAX should do. Practically, it runs into several issues.
29 // #1) Many computations on Retina devices overflow with that value.
30 // #2) In this particular use case, it results in the message
31 //     "CGAffineTransformInvert: singular matrix."
32 const CGFloat kInfiniteSize = 1.0e6;
34 // A line fragment padding that creates the same visual look as text layout in
35 // an NSTextField does. (Which UX feedback was based on)
36 const CGFloat kLineFragmentPadding = 2.0;
38 // Padding added on top of the label so its first line looks centered with
39 // respect to the input field. Only added when the input field is showing.
40 const CGFloat kLabelWithInputTopPadding = 5.0;
44 // An attachment cell for a single icon - takes care of proper alignment of
45 // text and icon.
46 @interface IconAttachmentCell : NSTextAttachmentCell {
47   CGFloat baseline_;  // The cell's baseline adjustment.
50 // Adjust the cell's baseline so that the lower edge of the image aligns with
51 // the longest descender, not the font baseline
52 - (void)adjustBaselineForFont:(NSFont*)font;
54 @end
57 @interface AutofillSuggestionView : NSView {
58  @private
59   // The main input field - only view not ignoring mouse events.
60   NSView* inputField_;
63 @property (assign, nonatomic) NSView* inputField;
65 @end
68 // The suggestion container should ignore any mouse events unless they occur
69 // within the bounds of an editable field.
70 @implementation AutofillSuggestionView
72 @synthesize inputField = inputField_;
74 - (NSView*)hitTest:(NSPoint)point {
75   NSView* hitView = [super hitTest:point];
76   if ([hitView isDescendantOf:inputField_])
77     return hitView;
79   return nil;
82 @end
85 @implementation IconAttachmentCell
87 - (NSPoint)cellBaselineOffset {
88   return NSMakePoint(0.0, baseline_);
91 // Ensure proper padding between text and icon.
92 - (NSSize)cellSize {
93   NSSize size = [super cellSize];
94   size.width += kAroundTextPadding;
95   return size;
98 // drawWithFrame: needs to be overridden to left-align the image. Default
99 // rendering centers images in the cell's frame.
100 - (void)drawWithFrame:(NSRect)frame inView:(NSView*)view {
101   frame.size.width -= kAroundTextPadding;
102   [super drawWithFrame:frame inView:view];
105 - (void)adjustBaselineForFont:(NSFont*)font {
106   CGFloat lineHeight = [font ascender];
107   baseline_ = std::floor((lineHeight - [[self image] size].height) / 2.0);
110 @end
113 @interface AutofillSuggestionContainer (Private)
115 // Set the main suggestion text and the corresponding |icon|.
116 // Attempts to wrap the text if |wrapText| is set.
117 - (void)setSuggestionText:(NSString*)line
118                      icon:(NSImage*)icon
119                  wrapText:(BOOL)wrapText;
121 @end
124 @implementation AutofillSuggestionContainer
126 - (AutofillTextField*)inputField {
127   return inputField_.get();
130 - (NSTextField*)makeDetailSectionLabel:(NSString*)labelText {
131   base::scoped_nsobject<NSTextField> label([[NSTextField alloc] init]);
132   [label setFont:
133       [[NSFontManager sharedFontManager] convertFont:[label font]
134                                          toHaveTrait:NSBoldFontMask]];
135   [label setStringValue:labelText];
136   [label setEditable:NO];
137   [label setBordered:NO];
138   [label sizeToFit];
139   return label.autorelease();
142 - (void)loadView {
143   label_.reset([[NSTextView alloc] initWithFrame:NSZeroRect]);
144   [[label_ textContainer] setLineFragmentPadding:kLineFragmentPadding];
145   [label_ setEditable:NO];
146   [label_ setSelectable:NO];
147   [label_ setDrawsBackground:NO];
149   base::scoped_nsobject<NSMutableParagraphStyle> paragraphStyle(
150       [[NSMutableParagraphStyle alloc] init]);
151   [paragraphStyle setLineSpacing:0.5 * [[label_ font] pointSize]];
152   [label_ setDefaultParagraphStyle:paragraphStyle];
154   inputField_.reset([[AutofillTextField alloc] initWithFrame:NSZeroRect]);
155   [inputField_ setHidden:YES];
157   spacer_.reset([[NSBox alloc] initWithFrame:NSZeroRect]);
158   [spacer_ setBoxType:NSBoxSeparator];
159   [spacer_ setBorderType:NSLineBorder];
161   base::scoped_nsobject<AutofillSuggestionView> view(
162       [[AutofillSuggestionView alloc] initWithFrame:NSZeroRect]);
163   [view setSubviews:
164       @[ label_, inputField_, spacer_ ]];
165   [view setInputField:inputField_];
166   [self setView:view];
169 - (void)setSuggestionText:(NSString*)line
170                      icon:(NSImage*)icon
171                  wrapText:(BOOL)wrapText {
172   [label_ setString:@""];
174   if ([icon size].width) {
175     base::scoped_nsobject<IconAttachmentCell> cell(
176         [[IconAttachmentCell alloc] initImageCell:icon]);
177     base::scoped_nsobject<NSTextAttachment> attachment(
178         [[NSTextAttachment alloc] init]);
179     [cell adjustBaselineForFont:[NSFont controlContentFontOfSize:0]];
180     [cell setAlignment:NSLeftTextAlignment];
181     [attachment setAttachmentCell:cell];
182     [[label_ textStorage] setAttributedString:
183         [NSAttributedString attributedStringWithAttachment:attachment]];
184   }
186   NSDictionary* attributes = @{
187     NSParagraphStyleAttributeName : [label_ defaultParagraphStyle],
188             NSCursorAttributeName : [NSCursor arrowCursor],
189               NSFontAttributeName : [NSFont controlContentFontOfSize:0]
190   };
191   base::scoped_nsobject<NSAttributedString> str1(
192       [[NSAttributedString alloc] initWithString:line
193                                       attributes:attributes]);
194   [[label_ textStorage] appendAttributedString:str1];
196   [label_ setVerticallyResizable:YES];
197   [label_ setHorizontallyResizable:!wrapText];
198   if (wrapText) {
199     CGFloat availableWidth =
200         4 * autofill::kFieldWidth - [inputField_ frame].size.width;
201     [label_ setFrameSize:NSMakeSize(availableWidth, kInfiniteSize)];
202   } else {
203     [label_ setFrameSize:NSMakeSize(kInfiniteSize, kInfiniteSize)];
204   }
205   [[label_ layoutManager] ensureLayoutForTextContainer:[label_ textContainer]];
206   [label_ sizeToFit];
209 - (void)
210     setSuggestionWithVerticallyCompactText:(NSString*)verticallyCompactText
211                    horizontallyCompactText:(NSString*)horizontallyCompactText
212                                       icon:(NSImage*)icon
213                                   maxWidth:(CGFloat)maxWidth {
214   // Prefer the vertically compact text when it fits. If it doesn't fit, fall
215   // back to the horizontally compact text.
216   [self setSuggestionText:verticallyCompactText icon:icon wrapText:NO];
217   if ([self preferredSize].width > maxWidth)
218     [self setSuggestionText:horizontallyCompactText icon:icon wrapText:YES];
222 - (void)showInputField:(NSString*)text withIcon:(NSImage*)icon {
223   [[inputField_ cell] setPlaceholderString:text];
224   [[inputField_ cell] setIcon:icon];
225   [inputField_ setHidden:NO];
226   [inputField_ sizeToFit];
228   // Enforce fixed width.
229   NSSize frameSize = NSMakeSize(autofill::kFieldWidth,
230                                 NSHeight([inputField_ frame]));
231   [inputField_ setFrameSize:frameSize];
235 - (NSSize)preferredSize {
236   NSSize size = [label_ bounds].size;
238   // Final inputField_ sizing/spacing depends on a TODO(estade) in Views code.
239   if (![inputField_ isHidden]) {
240     size.height = std::max(size.height + kLabelWithInputTopPadding,
241                            NSHeight([inputField_ frame]));
242     size.width += NSWidth([inputField_ frame])  + kAroundTextPadding;
243   }
245   size.height += kTopPadding;
247   return size;
250 - (void)performLayout {
251   NSRect bounds = [[self view] bounds];
252   NSSize preferredContainerSize = [self preferredSize];
253   // width is externally determined.
254   preferredContainerSize.width = NSWidth(bounds);
256   NSRect spacerFrame = NSMakeRect(0, preferredContainerSize.height - 1,
257                                   preferredContainerSize.width, 1);
259   NSRect labelFrame = [label_ bounds];
260   labelFrame.origin.x = NSMinX(bounds);
261   labelFrame.origin.y = NSMaxY(bounds) - NSHeight(labelFrame) - kTopPadding;
263   // Position input field - top is aligned to top of label field.
264   if (![inputField_ isHidden]) {
265     NSRect inputFieldFrame = [inputField_ frame];
266     inputFieldFrame.origin.x = NSMaxX(bounds) - NSWidth(inputFieldFrame);
267     inputFieldFrame.origin.y = NSMaxY(labelFrame) - NSHeight(inputFieldFrame);
268     [inputField_ setFrameOrigin:inputFieldFrame.origin];
270     // Vertically center the first line of the label with respect to the input
271     // field.
272     labelFrame.origin.y -= kLabelWithInputTopPadding;
274     // Due to fixed width, fields are guaranteed to not overlap.
275     DCHECK_LE(NSMaxX(labelFrame), NSMinX(inputFieldFrame));
276   }
278   [spacer_ setFrame:spacerFrame];
279   [label_ setFrame:labelFrame];
280   [[self view] setFrameSize:preferredContainerSize];
283 @end