Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(14)

Side by Side Diff: chrome/browser/ui/cocoa/autofill/autofill_section_container.mm

Issue 1931043002: Remove requestAutocomplete (Closed) Base URL: https://chromium.googlesource.com/chromium/src.git@master
Patch Set: rebase Created 4 years, 7 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch
OLDNEW
(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
OLDNEW

Powered by Google App Engine
This is Rietveld 408576698