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