OLD | NEW |
| (Empty) |
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. | |
4 | |
5 #import "chrome/browser/ui/cocoa/autofill/autofill_suggestion_container.h" | |
6 | |
7 #include <algorithm> | |
8 #include <cmath> | |
9 | |
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" | |
18 | |
19 namespace { | |
20 | |
21 // Horizontal padding between text and other elements (in pixels). | |
22 const int kAroundTextPadding = 4; | |
23 | |
24 // Padding at the top of suggestions. | |
25 const CGFloat kTopPadding = 10; | |
26 | |
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; | |
33 | |
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; | |
37 | |
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; | |
41 | |
42 } | |
43 | |
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. | |
48 } | |
49 | |
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; | |
53 | |
54 @end | |
55 | |
56 | |
57 @interface AutofillSuggestionView : NSView { | |
58 @private | |
59 // The main input field - only view not ignoring mouse events. | |
60 NSView* inputField_; | |
61 } | |
62 | |
63 @property (assign, nonatomic) NSView* inputField; | |
64 | |
65 @end | |
66 | |
67 | |
68 // The suggestion container should ignore any mouse events unless they occur | |
69 // within the bounds of an editable field. | |
70 @implementation AutofillSuggestionView | |
71 | |
72 @synthesize inputField = inputField_; | |
73 | |
74 - (NSView*)hitTest:(NSPoint)point { | |
75 NSView* hitView = [super hitTest:point]; | |
76 if ([hitView isDescendantOf:inputField_]) | |
77 return hitView; | |
78 | |
79 return nil; | |
80 } | |
81 | |
82 @end | |
83 | |
84 | |
85 @implementation IconAttachmentCell | |
86 | |
87 - (NSPoint)cellBaselineOffset { | |
88 return NSMakePoint(0.0, baseline_); | |
89 } | |
90 | |
91 // Ensure proper padding between text and icon. | |
92 - (NSSize)cellSize { | |
93 NSSize size = [super cellSize]; | |
94 size.width += kAroundTextPadding; | |
95 return size; | |
96 } | |
97 | |
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]; | |
103 } | |
104 | |
105 - (void)adjustBaselineForFont:(NSFont*)font { | |
106 CGFloat lineHeight = [font ascender]; | |
107 baseline_ = std::floor((lineHeight - [[self image] size].height) / 2.0); | |
108 } | |
109 | |
110 @end | |
111 | |
112 | |
113 @interface AutofillSuggestionContainer (Private) | |
114 | |
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; | |
120 | |
121 @end | |
122 | |
123 | |
124 @implementation AutofillSuggestionContainer | |
125 | |
126 - (AutofillTextField*)inputField { | |
127 return inputField_.get(); | |
128 } | |
129 | |
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(); | |
140 } | |
141 | |
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]; | |
148 | |
149 base::scoped_nsobject<NSMutableParagraphStyle> paragraphStyle( | |
150 [[NSMutableParagraphStyle alloc] init]); | |
151 [paragraphStyle setLineSpacing:0.5 * [[label_ font] pointSize]]; | |
152 [label_ setDefaultParagraphStyle:paragraphStyle]; | |
153 | |
154 inputField_.reset([[AutofillTextField alloc] initWithFrame:NSZeroRect]); | |
155 [inputField_ setHidden:YES]; | |
156 | |
157 spacer_.reset([[NSBox alloc] initWithFrame:NSZeroRect]); | |
158 [spacer_ setBoxType:NSBoxSeparator]; | |
159 [spacer_ setBorderType:NSLineBorder]; | |
160 | |
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]; | |
167 } | |
168 | |
169 - (void)setSuggestionText:(NSString*)line | |
170 icon:(NSImage*)icon | |
171 wrapText:(BOOL)wrapText { | |
172 [label_ setString:@""]; | |
173 | |
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 } | |
185 | |
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]; | |
195 | |
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]; | |
207 } | |
208 | |
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]; | |
219 } | |
220 | |
221 | |
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]; | |
227 | |
228 // Enforce fixed width. | |
229 NSSize frameSize = NSMakeSize(autofill::kFieldWidth, | |
230 NSHeight([inputField_ frame])); | |
231 [inputField_ setFrameSize:frameSize]; | |
232 } | |
233 | |
234 | |
235 - (NSSize)preferredSize { | |
236 NSSize size = [label_ bounds].size; | |
237 | |
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 } | |
244 | |
245 size.height += kTopPadding; | |
246 | |
247 return size; | |
248 } | |
249 | |
250 - (void)performLayout { | |
251 NSRect bounds = [[self view] bounds]; | |
252 NSSize preferredContainerSize = [self preferredSize]; | |
253 // width is externally determined. | |
254 preferredContainerSize.width = NSWidth(bounds); | |
255 | |
256 NSRect spacerFrame = NSMakeRect(0, preferredContainerSize.height - 1, | |
257 preferredContainerSize.width, 1); | |
258 | |
259 NSRect labelFrame = [label_ bounds]; | |
260 labelFrame.origin.x = NSMinX(bounds); | |
261 labelFrame.origin.y = NSMaxY(bounds) - NSHeight(labelFrame) - kTopPadding; | |
262 | |
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]; | |
269 | |
270 // Vertically center the first line of the label with respect to the input | |
271 // field. | |
272 labelFrame.origin.y -= kLabelWithInputTopPadding; | |
273 | |
274 // Due to fixed width, fields are guaranteed to not overlap. | |
275 DCHECK_LE(NSMaxX(labelFrame), NSMinX(inputFieldFrame)); | |
276 } | |
277 | |
278 [spacer_ setFrame:spacerFrame]; | |
279 [label_ setFrame:labelFrame]; | |
280 [[self view] setFrameSize:preferredContainerSize]; | |
281 } | |
282 | |
283 @end | |
OLD | NEW |