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"
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"
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
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;
57 @interface AutofillSuggestionView : NSView {
59 // The main input field - only view not ignoring mouse events.
63 @property (assign, nonatomic) NSView* inputField;
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_])
85 @implementation IconAttachmentCell
87 - (NSPoint)cellBaselineOffset {
88 return NSMakePoint(0.0, baseline_);
91 // Ensure proper padding between text and icon.
93 NSSize size = [super cellSize];
94 size.width += kAroundTextPadding;
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);
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
119 wrapText:(BOOL)wrapText;
124 @implementation AutofillSuggestionContainer
126 - (AutofillTextField*)inputField {
127 return inputField_.get();
130 - (NSTextField*)makeDetailSectionLabel:(NSString*)labelText {
131 base::scoped_nsobject<NSTextField> label([[NSTextField alloc] init]);
133 [[NSFontManager sharedFontManager] convertFont:[label font]
134 toHaveTrait:NSBoldFontMask]];
135 [label setStringValue:labelText];
136 [label setEditable:NO];
137 [label setBordered:NO];
139 return label.autorelease();
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]);
164 @[ label_, inputField_, spacer_ ]];
165 [view setInputField:inputField_];
169 - (void)setSuggestionText:(NSString*)line
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]];
186 NSDictionary* attributes = @{
187 NSParagraphStyleAttributeName : [label_ defaultParagraphStyle],
188 NSCursorAttributeName : [NSCursor arrowCursor],
189 NSFontAttributeName : [NSFont controlContentFontOfSize:0]
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];
199 CGFloat availableWidth =
200 4 * autofill::kFieldWidth - [inputField_ frame].size.width;
201 [label_ setFrameSize:NSMakeSize(availableWidth, kInfiniteSize)];
203 [label_ setFrameSize:NSMakeSize(kInfiniteSize, kInfiniteSize)];
205 [[label_ layoutManager] ensureLayoutForTextContainer:[label_ textContainer]];
210 setSuggestionWithVerticallyCompactText:(NSString*)verticallyCompactText
211 horizontallyCompactText:(NSString*)horizontallyCompactText
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;
245 size.height += kTopPadding;
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
272 labelFrame.origin.y -= kLabelWithInputTopPadding;
274 // Due to fixed width, fields are guaranteed to not overlap.
275 DCHECK_LE(NSMaxX(labelFrame), NSMinX(inputFieldFrame));
278 [spacer_ setFrame:spacerFrame];
279 [label_ setFrame:labelFrame];
280 [[self view] setFrameSize:preferredContainerSize];