OLD | NEW |
| (Empty) |
1 // Copyright (c) 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_section_container.h" | |
6 | |
7 #include <stddef.h> | |
8 | |
9 #include <algorithm> | |
10 | |
11 #include "base/mac/foundation_util.h" | |
12 #include "base/mac/sdk_forward_declarations.h" | |
13 #include "base/strings/sys_string_conversions.h" | |
14 #include "base/strings/utf_string_conversions.h" | |
15 #include "chrome/browser/ui/autofill/autofill_dialog_view_delegate.h" | |
16 #include "chrome/browser/ui/chrome_style.h" | |
17 #import "chrome/browser/ui/cocoa/autofill/autofill_pop_up_button.h" | |
18 #import "chrome/browser/ui/cocoa/autofill/autofill_section_view.h" | |
19 #import "chrome/browser/ui/cocoa/autofill/autofill_suggestion_container.h" | |
20 #import "chrome/browser/ui/cocoa/autofill/autofill_textfield.h" | |
21 #import "chrome/browser/ui/cocoa/autofill/autofill_tooltip_controller.h" | |
22 #import "chrome/browser/ui/cocoa/autofill/layout_view.h" | |
23 #include "chrome/browser/ui/cocoa/autofill/simple_grid_layout.h" | |
24 #import "chrome/browser/ui/cocoa/image_button_cell.h" | |
25 #import "chrome/browser/ui/cocoa/menu_button.h" | |
26 #include "components/autofill/core/browser/autofill_type.h" | |
27 #include "content/public/browser/native_web_keyboard_event.h" | |
28 #include "grit/components_scaled_resources.h" | |
29 #include "grit/theme_resources.h" | |
30 #import "ui/base/cocoa/menu_controller.h" | |
31 #include "ui/base/l10n/l10n_util_mac.h" | |
32 #include "ui/base/models/combobox_model.h" | |
33 #include "ui/base/resource/resource_bundle.h" | |
34 | |
35 namespace { | |
36 | |
37 // Constants used for layouting controls. These variables are copied from | |
38 // "ui/views/layout/layout_constants.h". | |
39 | |
40 // Horizontal spacing between controls that are logically related. | |
41 const int kRelatedControlHorizontalSpacing = 8; | |
42 | |
43 // Vertical spacing between controls that are logically related. | |
44 const int kRelatedControlVerticalSpacing = 8; | |
45 | |
46 // TODO(estade): pull out these constants, and figure out better values | |
47 // for them. Note: These are duplicated from Views code. | |
48 | |
49 // Fixed width for the details section. | |
50 const int kDetailsWidth = 440; | |
51 | |
52 // Top/bottom inset for contents of a detail section. | |
53 const size_t kDetailSectionInset = 10; | |
54 | |
55 // Vertical padding around the section header. | |
56 const CGFloat kVerticalHeaderPadding = 6; | |
57 | |
58 // If the Autofill data comes from a credit card, make sure to overwrite the | |
59 // CC comboboxes (even if they already have something in them). If the | |
60 // Autofill data comes from an AutofillProfile, leave the comboboxes alone. | |
61 // TODO(groby): This kind of logic should _really_ live on the delegate. | |
62 bool ShouldOverwriteComboboxes(autofill::DialogSection section, | |
63 autofill::ServerFieldType type) { | |
64 if (autofill::AutofillType(type).group() != autofill::CREDIT_CARD) { | |
65 return false; | |
66 } | |
67 | |
68 return section == autofill::SECTION_CC; | |
69 } | |
70 | |
71 } // namespace | |
72 | |
73 @interface AutofillSectionContainer () | |
74 | |
75 // An input field has been edited or activated - inform the delegate and | |
76 // possibly reset the validity of the input (if it's a textfield). | |
77 - (void)fieldEditedOrActivated:(NSControl<AutofillInputField>*)field | |
78 edited:(BOOL)edited; | |
79 | |
80 // Convenience method to retrieve a field type via the control's tag. | |
81 - (autofill::ServerFieldType)fieldTypeForControl:(NSControl*)control; | |
82 | |
83 // Find the DetailInput* associated with a field type. | |
84 - (const autofill::DetailInput*)detailInputForType: | |
85 (autofill::ServerFieldType)type; | |
86 | |
87 // Takes an NSArray of controls and builds a FieldValueMap from them. | |
88 // Translates between Cocoa code and delegate, essentially. | |
89 // All controls must inherit from NSControl and conform to AutofillInputView. | |
90 - (void)fillDetailOutputs:(autofill::FieldValueMap*)outputs | |
91 fromControls:(NSArray*)controls; | |
92 | |
93 // Updates input fields based on delegate status. If |shouldClobber| is YES, | |
94 // will clobber existing data and reset fields to the initial values. | |
95 - (void)updateAndClobber:(BOOL)shouldClobber; | |
96 | |
97 // Return YES if this is a section that contains CC info. (And, more | |
98 // importantly, a potential CVV field) | |
99 - (BOOL)isCreditCardSection; | |
100 | |
101 // Create properly styled label for section. Autoreleased. | |
102 - (NSTextField*)makeDetailSectionLabel:(NSString*)labelText; | |
103 | |
104 // Create a button offering input suggestions. | |
105 - (MenuButton*)makeSuggestionButton; | |
106 | |
107 // Create a view with all inputs requested by |delegate_| and resets |input_|. | |
108 - (void)makeInputControls; | |
109 | |
110 // Refresh all field icons based on |delegate_| status. | |
111 - (void)updateFieldIcons; | |
112 | |
113 @end | |
114 | |
115 @implementation AutofillSectionContainer | |
116 | |
117 @synthesize section = section_; | |
118 @synthesize validationDelegate = validationDelegate_; | |
119 | |
120 - (id)initWithDelegate:(autofill::AutofillDialogViewDelegate*)delegate | |
121 forSection:(autofill::DialogSection)section { | |
122 if (self = [super init]) { | |
123 section_ = section; | |
124 delegate_ = delegate; | |
125 } | |
126 return self; | |
127 } | |
128 | |
129 - (void)getInputs:(autofill::FieldValueMap*)output { | |
130 [self fillDetailOutputs:output fromControls:[inputs_ subviews]]; | |
131 } | |
132 | |
133 // Note: This corresponds to Views' "UpdateDetailsGroupState". | |
134 - (void)modelChanged { | |
135 ui::MenuModel* suggestionModel = delegate_->MenuModelForSection(section_); | |
136 menuController_.reset([[MenuController alloc] initWithModel:suggestionModel | |
137 useWithPopUpButtonCell:YES]); | |
138 NSMenu* menu = [menuController_ menu]; | |
139 | |
140 const BOOL hasSuggestions = [menu numberOfItems] > 0; | |
141 [suggestButton_ setHidden:!hasSuggestions]; | |
142 | |
143 [suggestButton_ setAttachedMenu:menu]; | |
144 | |
145 [self updateSuggestionState]; | |
146 | |
147 if (![[self view] isHidden]) | |
148 [self validateFor:autofill::VALIDATE_EDIT]; | |
149 | |
150 // Always request re-layout on state change. | |
151 [self requestRelayout]; | |
152 } | |
153 | |
154 - (void)requestRelayout { | |
155 id delegate = [[view_ window] windowController]; | |
156 if ([delegate respondsToSelector:@selector(requestRelayout)]) | |
157 [delegate performSelector:@selector(requestRelayout)]; | |
158 } | |
159 | |
160 - (void)loadView { | |
161 [self makeInputControls]; | |
162 | |
163 base::string16 labelText = delegate_->LabelForSection(section_); | |
164 label_.reset( | |
165 [[self makeDetailSectionLabel:base::SysUTF16ToNSString(labelText)] | |
166 retain]); | |
167 | |
168 suggestButton_.reset([[self makeSuggestionButton] retain]); | |
169 suggestContainer_.reset([[AutofillSuggestionContainer alloc] init]); | |
170 | |
171 view_.reset([[AutofillSectionView alloc] initWithFrame:NSZeroRect]); | |
172 [self setView:view_]; | |
173 [view_ setSubviews: | |
174 @[label_, inputs_, [suggestContainer_ view], suggestButton_]]; | |
175 if (tooltipController_) { | |
176 [view_ addSubview:[tooltipController_ view] | |
177 positioned:NSWindowAbove | |
178 relativeTo:inputs_]; | |
179 } | |
180 | |
181 if ([self isCreditCardSection]) { | |
182 // Credit card sections *MUST* have a CREDIT_CARD_VERIFICATION_CODE input. | |
183 DCHECK([self detailInputForType:autofill::CREDIT_CARD_VERIFICATION_CODE]); | |
184 [[suggestContainer_ inputField] setTag: | |
185 autofill::CREDIT_CARD_VERIFICATION_CODE]; | |
186 [[suggestContainer_ inputField] setInputDelegate:self]; | |
187 } | |
188 | |
189 [self modelChanged]; | |
190 } | |
191 | |
192 - (NSSize)preferredSize { | |
193 if ([view_ isHidden]) | |
194 return NSZeroSize; | |
195 | |
196 NSSize labelSize = [label_ frame].size; // Assumes sizeToFit was called. | |
197 CGFloat controlHeight = [inputs_ preferredHeightForWidth:kDetailsWidth]; | |
198 if (showSuggestions_) | |
199 controlHeight = [suggestContainer_ preferredSize].height; | |
200 | |
201 return NSMakeSize(kDetailsWidth + 2 * chrome_style::kHorizontalPadding, | |
202 labelSize.height + kVerticalHeaderPadding + | |
203 controlHeight + 2 * kDetailSectionInset); | |
204 } | |
205 | |
206 - (void)performLayout { | |
207 if ([view_ isHidden]) | |
208 return; | |
209 | |
210 NSSize buttonSize = [suggestButton_ frame].size; // Assume sizeToFit. | |
211 NSSize labelSize = [label_ frame].size; // Assumes sizeToFit was called. | |
212 CGFloat controlHeight = [inputs_ preferredHeightForWidth:kDetailsWidth]; | |
213 if (showSuggestions_) | |
214 controlHeight = [suggestContainer_ preferredSize].height; | |
215 | |
216 NSRect viewFrame = NSZeroRect; | |
217 viewFrame.size = [self preferredSize]; | |
218 | |
219 NSRect contentFrame = NSInsetRect(viewFrame, | |
220 chrome_style::kHorizontalPadding, | |
221 kDetailSectionInset); | |
222 NSRect controlFrame, labelFrame, buttonFrame; | |
223 | |
224 // Label is top left, suggestion button is top right, controls are below that. | |
225 NSDivideRect(contentFrame, &labelFrame, &controlFrame, | |
226 kVerticalHeaderPadding + labelSize.height, NSMaxYEdge); | |
227 NSDivideRect(labelFrame, &buttonFrame, &labelFrame, | |
228 buttonSize.width, NSMaxXEdge); | |
229 | |
230 labelFrame = NSOffsetRect(labelFrame, 0, kVerticalHeaderPadding); | |
231 labelFrame.size = labelSize; | |
232 | |
233 buttonFrame = NSOffsetRect(buttonFrame, 0, 5); | |
234 buttonFrame.size = buttonSize; | |
235 | |
236 if (showSuggestions_) { | |
237 [[suggestContainer_ view] setFrame:controlFrame]; | |
238 [suggestContainer_ performLayout]; | |
239 } else { | |
240 [inputs_ setFrame:controlFrame]; | |
241 } | |
242 [label_ setFrame:labelFrame]; | |
243 [suggestButton_ setFrame:buttonFrame]; | |
244 [inputs_ setHidden:showSuggestions_]; | |
245 [[suggestContainer_ view] setHidden:!showSuggestions_]; | |
246 [view_ setFrameSize:viewFrame.size]; | |
247 if (tooltipController_) { | |
248 [[tooltipController_ view] setHidden:showSuggestions_]; | |
249 NSRect tooltipIconFrame = [tooltipField_ decorationFrame]; | |
250 tooltipIconFrame.origin = | |
251 [[self view] convertPoint:tooltipIconFrame.origin | |
252 fromView:[tooltipField_ superview]]; | |
253 [[tooltipController_ view] setFrame:tooltipIconFrame]; | |
254 } | |
255 } | |
256 | |
257 - (KeyEventHandled)keyEvent:(NSEvent*)event forInput:(id)sender { | |
258 content::NativeWebKeyboardEvent webEvent(event); | |
259 | |
260 // Only handle keyDown, to handle key repeats without duplicates. | |
261 if (webEvent.type != content::NativeWebKeyboardEvent::RawKeyDown) | |
262 return kKeyEventNotHandled; | |
263 | |
264 // Allow the delegate to intercept key messages. | |
265 if (delegate_->HandleKeyPressEventInInput(webEvent)) | |
266 return kKeyEventHandled; | |
267 return kKeyEventNotHandled; | |
268 } | |
269 | |
270 - (void)onMouseDown:(NSControl<AutofillInputField>*)field { | |
271 [self fieldEditedOrActivated:field edited:NO]; | |
272 [validationDelegate_ updateMessageForField:field]; | |
273 } | |
274 | |
275 - (void)fieldBecameFirstResponder:(NSControl<AutofillInputField>*)field { | |
276 [validationDelegate_ updateMessageForField:field]; | |
277 } | |
278 | |
279 - (void)didChange:(id)sender { | |
280 [self fieldEditedOrActivated:sender edited:YES]; | |
281 } | |
282 | |
283 - (void)didEndEditing:(id)sender { | |
284 delegate_->FocusMoved(); | |
285 [validationDelegate_ hideErrorBubble]; | |
286 [self validateFor:autofill::VALIDATE_EDIT]; | |
287 } | |
288 | |
289 - (void)updateSuggestionState { | |
290 const autofill::SuggestionState& suggestionState = | |
291 delegate_->SuggestionStateForSection(section_); | |
292 showSuggestions_ = suggestionState.visible; | |
293 | |
294 if (!suggestionState.extra_text.empty()) { | |
295 NSString* extraText = | |
296 base::SysUTF16ToNSString(suggestionState.extra_text); | |
297 NSImage* extraIcon = suggestionState.extra_icon.AsNSImage(); | |
298 [suggestContainer_ showInputField:extraText withIcon:extraIcon]; | |
299 } | |
300 | |
301 // NOTE: It's important to set the input field, if there is one, _before_ | |
302 // setting the suggestion text, since the suggestion container needs to | |
303 // account for the input field's width when deciding which of the two string | |
304 // representations to use. | |
305 NSString* verticallyCompactText = | |
306 base::SysUTF16ToNSString(suggestionState.vertically_compact_text); | |
307 NSString* horizontallyCompactText = | |
308 base::SysUTF16ToNSString(suggestionState.horizontally_compact_text); | |
309 [suggestContainer_ | |
310 setSuggestionWithVerticallyCompactText:verticallyCompactText | |
311 horizontallyCompactText:horizontallyCompactText | |
312 icon:suggestionState.icon.AsNSImage() | |
313 maxWidth:kDetailsWidth]; | |
314 | |
315 [view_ setShouldHighlightOnHover:showSuggestions_]; | |
316 if (showSuggestions_) | |
317 [view_ setClickTarget:suggestButton_]; | |
318 else | |
319 [view_ setClickTarget:nil]; | |
320 [view_ setHidden:!delegate_->SectionIsActive(section_)]; | |
321 } | |
322 | |
323 - (void)update { | |
324 [self updateAndClobber:YES]; | |
325 [view_ updateHoverState]; | |
326 } | |
327 | |
328 - (void)fillForType:(const autofill::ServerFieldType)type { | |
329 // Make sure to overwrite the originating input if it is a text field. | |
330 AutofillTextField* field = | |
331 base::mac::ObjCCast<AutofillTextField>([inputs_ viewWithTag:type]); | |
332 [field setFieldValue:@""]; | |
333 | |
334 if (ShouldOverwriteComboboxes(section_, type)) { | |
335 for (NSControl* control in [inputs_ subviews]) { | |
336 AutofillPopUpButton* popup = | |
337 base::mac::ObjCCast<AutofillPopUpButton>(control); | |
338 if (popup) { | |
339 autofill::ServerFieldType fieldType = | |
340 [self fieldTypeForControl:popup]; | |
341 if (autofill::AutofillType(fieldType).group() == | |
342 autofill::CREDIT_CARD) { | |
343 ui::ComboboxModel* model = | |
344 delegate_->ComboboxModelForAutofillType(fieldType); | |
345 DCHECK(model); | |
346 [popup selectItemAtIndex:model->GetDefaultIndex()]; | |
347 } | |
348 } | |
349 } | |
350 } | |
351 | |
352 [self updateAndClobber:NO]; | |
353 } | |
354 | |
355 - (BOOL)validateFor:(autofill::ValidationType)validationType { | |
356 NSArray* fields = nil; | |
357 if (!showSuggestions_) { | |
358 fields = [inputs_ subviews]; | |
359 } else if ([self isCreditCardSection]) { | |
360 if (![[suggestContainer_ inputField] isHidden]) | |
361 fields = @[ [suggestContainer_ inputField] ]; | |
362 } | |
363 | |
364 // Ensure only editable fields are validated. | |
365 fields = [fields filteredArrayUsingPredicate: | |
366 [NSPredicate predicateWithBlock: | |
367 ^BOOL(NSControl<AutofillInputField>* field, NSDictionary* bindings) { | |
368 return [field isEnabled]; | |
369 }]]; | |
370 | |
371 autofill::FieldValueMap detailOutputs; | |
372 [self fillDetailOutputs:&detailOutputs fromControls:fields]; | |
373 autofill::ValidityMessages messages = delegate_->InputsAreValid( | |
374 section_, detailOutputs); | |
375 | |
376 for (NSControl<AutofillInputField>* input in fields) { | |
377 const autofill::ValidityMessage& message = | |
378 messages.GetMessageOrDefault([self fieldTypeForControl:input]); | |
379 if (validationType != autofill::VALIDATE_FINAL && !message.sure) | |
380 continue; | |
381 [input setValidityMessage:base::SysUTF16ToNSString(message.text)]; | |
382 [validationDelegate_ updateMessageForField:input]; | |
383 } | |
384 | |
385 return !messages.HasErrors(); | |
386 } | |
387 | |
388 - (NSString*)suggestionText { | |
389 return showSuggestions_ ? [[suggestContainer_ inputField] stringValue] : nil; | |
390 } | |
391 | |
392 - (void)addInputsToArray:(NSMutableArray*)array { | |
393 [array addObjectsFromArray:[inputs_ subviews]]; | |
394 | |
395 // Only credit card sections can have a suggestion input. | |
396 if ([self isCreditCardSection]) | |
397 [array addObject:[suggestContainer_ inputField]]; | |
398 } | |
399 | |
400 #pragma mark Internal API for AutofillSectionContainer. | |
401 | |
402 - (void)fieldEditedOrActivated:(NSControl<AutofillInputField>*)field | |
403 edited:(BOOL)edited { | |
404 autofill::ServerFieldType type = [self fieldTypeForControl:field]; | |
405 base::string16 fieldValue = base::SysNSStringToUTF16([field fieldValue]); | |
406 | |
407 // Get the frame rectangle for the designated field, in screen coordinates. | |
408 NSRect textFrameInScreen = [field convertRect:[field bounds] toView:nil]; | |
409 textFrameInScreen = [[field window] convertRectToScreen:textFrameInScreen]; | |
410 | |
411 // And adjust for gfx::Rect being flipped compared to OSX coordinates. | |
412 NSScreen* screen = [[NSScreen screens] firstObject]; | |
413 textFrameInScreen.origin.y = | |
414 NSMaxY([screen frame]) - NSMaxY(textFrameInScreen); | |
415 gfx::Rect textFrameRect(NSRectToCGRect(textFrameInScreen)); | |
416 | |
417 delegate_->UserEditedOrActivatedInput(section_, | |
418 type, | |
419 [self view], | |
420 textFrameRect, | |
421 fieldValue, | |
422 edited); | |
423 | |
424 AutofillTextField* textfield = base::mac::ObjCCast<AutofillTextField>(field); | |
425 if (!textfield) | |
426 return; | |
427 | |
428 // If the field is marked as invalid, check if the text is now valid. Many | |
429 // fields (i.e. CC#) are invalid for most of the duration of editing, so | |
430 // flagging them as invalid prematurely is not helpful. However, correcting a | |
431 // minor mistake (i.e. a wrong CC digit) should immediately result in | |
432 // validation - positive user feedback. | |
433 if ([textfield invalid] && edited) { | |
434 base::string16 message = delegate_->InputValidityMessage(section_, | |
435 type, | |
436 fieldValue); | |
437 [textfield setValidityMessage:base::SysUTF16ToNSString(message)]; | |
438 | |
439 // If the field transitioned from invalid to valid, re-validate the group, | |
440 // since inter-field checks become meaningful with valid fields. | |
441 if (![textfield invalid]) | |
442 [self validateFor:autofill::VALIDATE_EDIT]; | |
443 | |
444 // The validity message has potentially changed - notify the error bubble. | |
445 [validationDelegate_ updateMessageForField:textfield]; | |
446 } | |
447 | |
448 // Update the icon if necessary. | |
449 if (delegate_->FieldControlsIcons(type)) | |
450 [self updateFieldIcons]; | |
451 } | |
452 | |
453 - (autofill::ServerFieldType)fieldTypeForControl:(NSControl*)control { | |
454 DCHECK([control tag]); | |
455 return static_cast<autofill::ServerFieldType>([control tag]); | |
456 } | |
457 | |
458 - (const autofill::DetailInput*)detailInputForType: | |
459 (autofill::ServerFieldType)type { | |
460 for (size_t i = 0; i < detailInputs_.size(); ++i) { | |
461 if (detailInputs_[i]->type == type) | |
462 return detailInputs_[i]; | |
463 } | |
464 // TODO(groby): Needs to be NOTREACHED. Can't, due to the fact that tests | |
465 // blindly call setFieldValue:forType:, even for non-existing inputs. | |
466 return NULL; | |
467 } | |
468 | |
469 - (void)fillDetailOutputs:(autofill::FieldValueMap*)outputs | |
470 fromControls:(NSArray*)controls { | |
471 for (NSControl<AutofillInputField>* input in controls) { | |
472 DCHECK([input isKindOfClass:[NSControl class]]); | |
473 DCHECK([input conformsToProtocol:@protocol(AutofillInputField)]); | |
474 outputs->insert(std::make_pair( | |
475 [self fieldTypeForControl:input], | |
476 base::SysNSStringToUTF16([input fieldValue]))); | |
477 } | |
478 } | |
479 | |
480 - (NSTextField*)makeDetailSectionLabel:(NSString*)labelText { | |
481 base::scoped_nsobject<NSTextField> label([[NSTextField alloc] init]); | |
482 [label setFont: | |
483 [[NSFontManager sharedFontManager] convertFont:[label font] | |
484 toHaveTrait:NSBoldFontMask]]; | |
485 [label setStringValue:labelText]; | |
486 [label setEditable:NO]; | |
487 [label setBordered:NO]; | |
488 [label setDrawsBackground:NO]; | |
489 [label sizeToFit]; | |
490 return label.autorelease(); | |
491 } | |
492 | |
493 - (void)updateAndClobber:(BOOL)shouldClobber { | |
494 if (shouldClobber) { | |
495 // Remember which one of the inputs was first responder so focus can be | |
496 // restored after the inputs are rebuilt. | |
497 NSView* firstResponderView = | |
498 base::mac::ObjCCast<NSView>([[inputs_ window] firstResponder]); | |
499 autofill::ServerFieldType type = autofill::UNKNOWN_TYPE; | |
500 for (NSControl* field in [inputs_ subviews]) { | |
501 if ([firstResponderView isDescendantOf:field]) { | |
502 type = [self fieldTypeForControl:field]; | |
503 break; | |
504 } | |
505 } | |
506 | |
507 [self makeInputControls]; | |
508 | |
509 if (type != autofill::UNKNOWN_TYPE) { | |
510 NSView* view = [inputs_ viewWithTag:type]; | |
511 if (view) | |
512 [[inputs_ window] makeFirstResponder:view]; | |
513 } | |
514 } else { | |
515 const autofill::DetailInputs& updatedInputs = | |
516 delegate_->RequestedFieldsForSection(section_); | |
517 | |
518 for (autofill::DetailInputs::const_iterator iter = updatedInputs.begin(); | |
519 iter != updatedInputs.end(); | |
520 ++iter) { | |
521 NSControl<AutofillInputField>* field = [inputs_ viewWithTag:iter->type]; | |
522 DCHECK(field); | |
523 if ([field isDefault]) | |
524 [field setFieldValue:base::SysUTF16ToNSString(iter->initial_value)]; | |
525 } | |
526 [self updateFieldIcons]; | |
527 } | |
528 | |
529 [self modelChanged]; | |
530 } | |
531 | |
532 - (BOOL)isCreditCardSection { | |
533 return section_ == autofill::SECTION_CC; | |
534 } | |
535 | |
536 - (MenuButton*)makeSuggestionButton { | |
537 base::scoped_nsobject<MenuButton> button([[MenuButton alloc] init]); | |
538 | |
539 [button setOpenMenuOnClick:YES]; | |
540 [button setBordered:NO]; | |
541 [button setShowsBorderOnlyWhileMouseInside:YES]; | |
542 | |
543 ui::ResourceBundle& rb = ui::ResourceBundle::GetSharedInstance(); | |
544 NSImage* image = | |
545 rb.GetNativeImageNamed(IDR_AUTOFILL_DIALOG_MENU_BUTTON).ToNSImage(); | |
546 [[button cell] setImage:image | |
547 forButtonState:image_button_cell::kDefaultState]; | |
548 image = rb.GetNativeImageNamed(IDR_AUTOFILL_DIALOG_MENU_BUTTON_H). | |
549 ToNSImage(); | |
550 [[button cell] setImage:image | |
551 forButtonState:image_button_cell::kHoverState]; | |
552 image = rb.GetNativeImageNamed(IDR_AUTOFILL_DIALOG_MENU_BUTTON_P). | |
553 ToNSImage(); | |
554 [[button cell] setImage:image | |
555 forButtonState:image_button_cell::kPressedState]; | |
556 image = rb.GetNativeImageNamed(IDR_AUTOFILL_DIALOG_MENU_BUTTON_D). | |
557 ToNSImage(); | |
558 [[button cell] setImage:image | |
559 forButtonState:image_button_cell::kDisabledState]; | |
560 | |
561 // ImageButtonCell's cellSize is not working. (http://crbug.com/298501) | |
562 [button setFrameSize:[image size]]; | |
563 return button.autorelease(); | |
564 } | |
565 | |
566 // TODO(estade): we should be using Chrome-style constrained window padding | |
567 // values. | |
568 - (void)makeInputControls { | |
569 if (inputs_) { | |
570 // When |inputs_| is replaced in response to a country change, there's a | |
571 // didEndEditing dispatched that segfaults or DCHECKS() as it's operating on | |
572 // stale input fields. Nil out the input delegate so this doesn't happen. | |
573 for (NSControl<AutofillInputField>* input in [inputs_ subviews]) { | |
574 [input setInputDelegate:nil]; | |
575 } | |
576 } | |
577 | |
578 detailInputs_.clear(); | |
579 | |
580 // Keep a list of weak pointers to DetailInputs. | |
581 const autofill::DetailInputs& inputs = | |
582 delegate_->RequestedFieldsForSection(section_); | |
583 | |
584 // Reverse the order of all the inputs. | |
585 for (int i = inputs.size() - 1; i >= 0; --i) { | |
586 detailInputs_.push_back(&(inputs[i])); | |
587 } | |
588 | |
589 // Then right the reversal in each row. | |
590 std::vector<const autofill::DetailInput*>::iterator it; | |
591 for (it = detailInputs_.begin(); it < detailInputs_.end(); ++it) { | |
592 std::vector<const autofill::DetailInput*>::iterator start = it; | |
593 while (it != detailInputs_.end() && | |
594 (*it)->length != autofill::DetailInput::LONG) { | |
595 ++it; | |
596 } | |
597 std::reverse(start, it); | |
598 } | |
599 | |
600 base::scoped_nsobject<LayoutView> view([[LayoutView alloc] init]); | |
601 [view setLayoutManager:std::unique_ptr<SimpleGridLayout>( | |
602 new SimpleGridLayout(view))]; | |
603 SimpleGridLayout* layout = [view layoutManager]; | |
604 | |
605 int column_set_id = 0; | |
606 for (size_t i = 0; i < detailInputs_.size(); ++i) { | |
607 const autofill::DetailInput& input = *detailInputs_[i]; | |
608 | |
609 if (input.length == autofill::DetailInput::LONG) | |
610 ++column_set_id; | |
611 | |
612 int kColumnSetId = | |
613 input.length == autofill::DetailInput::NONE ? -1 : column_set_id; | |
614 | |
615 ColumnSet* columnSet = layout->GetColumnSet(kColumnSetId); | |
616 if (!columnSet) { | |
617 // Create a new column set and row. | |
618 columnSet = layout->AddColumnSet(kColumnSetId); | |
619 if (i != 0 && kColumnSetId != -1) | |
620 layout->AddPaddingRow(kRelatedControlVerticalSpacing); | |
621 layout->StartRow(0, kColumnSetId); | |
622 } else { | |
623 // Add a new column to existing row. | |
624 columnSet->AddPaddingColumn(kRelatedControlHorizontalSpacing); | |
625 // Must explicitly skip the padding column since we've already started | |
626 // adding views. | |
627 layout->SkipColumns(1); | |
628 } | |
629 | |
630 columnSet->AddColumn(input.expand_weight ? input.expand_weight : 1.0f); | |
631 | |
632 ui::ComboboxModel* inputModel = | |
633 delegate_->ComboboxModelForAutofillType(input.type); | |
634 base::scoped_nsprotocol<NSControl<AutofillInputField>*> control; | |
635 if (inputModel) { | |
636 base::scoped_nsobject<AutofillPopUpButton> popup( | |
637 [[AutofillPopUpButton alloc] initWithFrame:NSZeroRect pullsDown:NO]); | |
638 for (int i = 0; i < inputModel->GetItemCount(); ++i) { | |
639 if (!inputModel->IsItemSeparatorAt(i)) { | |
640 // Currently, the first item in |inputModel| is duplicated later in | |
641 // the list. The second item is a separator. Because NSPopUpButton | |
642 // de-duplicates, the menu's just left with a separator on the top of | |
643 // the list (with nothing it's separating). For that reason, | |
644 // separators are ignored on Mac for now. http://crbug.com/347653 | |
645 [popup addItemWithTitle: | |
646 base::SysUTF16ToNSString(inputModel->GetItemAt(i))]; | |
647 } | |
648 } | |
649 [popup setDefaultValue:base::SysUTF16ToNSString( | |
650 inputModel->GetItemAt(inputModel->GetDefaultIndex()))]; | |
651 control.reset(popup.release()); | |
652 } else { | |
653 base::scoped_nsobject<AutofillTextField> field( | |
654 [[AutofillTextField alloc] init]); | |
655 [field setIsMultiline:input.IsMultiline()]; | |
656 [[field cell] setLineBreakMode:NSLineBreakByClipping]; | |
657 [[field cell] setScrollable:YES]; | |
658 [[field cell] setPlaceholderString: | |
659 l10n_util::FixUpWindowsStyleLabel(input.placeholder_text)]; | |
660 NSString* tooltipText = | |
661 base::SysUTF16ToNSString(delegate_->TooltipForField(input.type)); | |
662 // VoiceOver onlys seems to pick up the help message on [field cell] | |
663 // (rather than just field). | |
664 BOOL success = [[field cell] | |
665 accessibilitySetOverrideValue:tooltipText | |
666 forAttribute:NSAccessibilityHelpAttribute]; | |
667 DCHECK(success); | |
668 if ([tooltipText length] > 0) { | |
669 if (!tooltipController_) { | |
670 tooltipController_.reset( | |
671 [[AutofillTooltipController alloc] | |
672 initWithArrowLocation:info_bubble::kTopRight]); | |
673 } | |
674 tooltipField_ = field.get(); | |
675 NSImage* icon = | |
676 ui::ResourceBundle::GetSharedInstance().GetNativeImageNamed( | |
677 IDR_AUTOFILL_TOOLTIP_ICON).ToNSImage(); | |
678 [tooltipController_ setImage:icon]; | |
679 [tooltipController_ setMessage:tooltipText]; | |
680 [[field cell] setDecorationSize:[icon size]]; | |
681 } | |
682 [field setDefaultValue:@""]; | |
683 control.reset(field.release()); | |
684 } | |
685 [control setTag:input.type]; | |
686 [control setFieldValue:base::SysUTF16ToNSString(input.initial_value)]; | |
687 [control sizeToFit]; | |
688 [control setFrame:NSIntegralRect([control frame])]; | |
689 [control setInputDelegate:self]; | |
690 // Hide away fields that cannot be edited. | |
691 if (kColumnSetId == -1) { | |
692 [control setFrame:NSZeroRect]; | |
693 [control setHidden:YES]; | |
694 } | |
695 layout->AddView(control); | |
696 | |
697 if (input.length == autofill::DetailInput::LONG || | |
698 input.length == autofill::DetailInput::SHORT_EOL) { | |
699 ++column_set_id; | |
700 } | |
701 } | |
702 | |
703 if (inputs_) { | |
704 [[self view] replaceSubview:inputs_ with:view]; | |
705 [self requestRelayout]; | |
706 } | |
707 | |
708 inputs_ = view; | |
709 [self updateFieldIcons]; | |
710 } | |
711 | |
712 - (void)updateFieldIcons { | |
713 autofill::FieldValueMap fieldValues; | |
714 for (NSControl<AutofillInputField>* input in [inputs_ subviews]) { | |
715 DCHECK([input isKindOfClass:[NSControl class]]); | |
716 DCHECK([input conformsToProtocol:@protocol(AutofillInputField)]); | |
717 autofill::ServerFieldType fieldType = [self fieldTypeForControl:input]; | |
718 NSString* value = [input fieldValue]; | |
719 fieldValues[fieldType] = base::SysNSStringToUTF16(value); | |
720 } | |
721 | |
722 autofill::FieldIconMap fieldIcons = delegate_->IconsForFields(fieldValues); | |
723 for (autofill::FieldIconMap::const_iterator iter = fieldIcons.begin(); | |
724 iter!= fieldIcons.end(); ++iter) { | |
725 AutofillTextField* textfield = base::mac::ObjCCastStrict<AutofillTextField>( | |
726 [inputs_ viewWithTag:iter->first]); | |
727 [[textfield cell] setIcon:iter->second.ToNSImage()]; | |
728 } | |
729 } | |
730 | |
731 @end | |
732 | |
733 | |
734 @implementation AutofillSectionContainer (ForTesting) | |
735 | |
736 - (NSControl*)getField:(autofill::ServerFieldType)type { | |
737 return [inputs_ viewWithTag:type]; | |
738 } | |
739 | |
740 - (void)setFieldValue:(NSString*)text | |
741 forType:(autofill::ServerFieldType)type { | |
742 NSControl<AutofillInputField>* field = [inputs_ viewWithTag:type]; | |
743 if (field) | |
744 [field setFieldValue:text]; | |
745 } | |
746 | |
747 - (void)setSuggestionFieldValue:(NSString*)text { | |
748 [[suggestContainer_ inputField] setFieldValue:text]; | |
749 } | |
750 | |
751 - (void)activateFieldForType:(autofill::ServerFieldType)type { | |
752 NSControl<AutofillInputField>* field = [inputs_ viewWithTag:type]; | |
753 if (field) { | |
754 [[field window] makeFirstResponder:field]; | |
755 [self fieldEditedOrActivated:field edited:NO]; | |
756 } | |
757 } | |
758 | |
759 @end | |
OLD | NEW |