Upstream version 5.34.104.0
[platform/framework/web/crosswalk.git] / src / chrome / browser / ui / views / autofill / autofill_dialog_views.cc
1 // Copyright (c) 2012 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 #include "chrome/browser/ui/views/autofill/autofill_dialog_views.h"
6
7 #include <utility>
8
9 #include "base/bind.h"
10 #include "base/location.h"
11 #include "base/strings/utf_string_conversions.h"
12 #include "chrome/browser/profiles/profile.h"
13 #include "chrome/browser/ui/autofill/autofill_dialog_sign_in_delegate.h"
14 #include "chrome/browser/ui/autofill/autofill_dialog_view_delegate.h"
15 #include "chrome/browser/ui/autofill/loading_animation.h"
16 #include "chrome/browser/ui/views/autofill/decorated_textfield.h"
17 #include "chrome/browser/ui/views/autofill/info_bubble.h"
18 #include "chrome/browser/ui/views/autofill/tooltip_icon.h"
19 #include "chrome/browser/ui/views/constrained_window_views.h"
20 #include "components/autofill/content/browser/wallet/wallet_service_url.h"
21 #include "components/autofill/core/browser/autofill_type.h"
22 #include "components/web_modal/web_contents_modal_dialog_host.h"
23 #include "components/web_modal/web_contents_modal_dialog_manager.h"
24 #include "components/web_modal/web_contents_modal_dialog_manager_delegate.h"
25 #include "content/public/browser/native_web_keyboard_event.h"
26 #include "content/public/browser/navigation_controller.h"
27 #include "content/public/browser/web_contents.h"
28 #include "content/public/browser/web_contents_view.h"
29 #include "grit/theme_resources.h"
30 #include "grit/ui_resources.h"
31 #include "third_party/skia/include/core/SkColor.h"
32 #include "ui/base/l10n/l10n_util.h"
33 #include "ui/base/models/combobox_model.h"
34 #include "ui/base/models/menu_model.h"
35 #include "ui/base/resource/resource_bundle.h"
36 #include "ui/gfx/animation/animation_delegate.h"
37 #include "ui/gfx/canvas.h"
38 #include "ui/gfx/color_utils.h"
39 #include "ui/gfx/font_list.h"
40 #include "ui/gfx/path.h"
41 #include "ui/gfx/point.h"
42 #include "ui/gfx/skia_util.h"
43 #include "ui/views/background.h"
44 #include "ui/views/border.h"
45 #include "ui/views/bubble/bubble_border.h"
46 #include "ui/views/bubble/bubble_frame_view.h"
47 #include "ui/views/controls/button/blue_button.h"
48 #include "ui/views/controls/button/checkbox.h"
49 #include "ui/views/controls/button/label_button.h"
50 #include "ui/views/controls/button/label_button_border.h"
51 #include "ui/views/controls/button/menu_button.h"
52 #include "ui/views/controls/combobox/combobox.h"
53 #include "ui/views/controls/image_view.h"
54 #include "ui/views/controls/label.h"
55 #include "ui/views/controls/link.h"
56 #include "ui/views/controls/menu/menu_runner.h"
57 #include "ui/views/controls/separator.h"
58 #include "ui/views/controls/styled_label.h"
59 #include "ui/views/controls/textfield/textfield.h"
60 #include "ui/views/controls/webview/webview.h"
61 #include "ui/views/layout/box_layout.h"
62 #include "ui/views/layout/fill_layout.h"
63 #include "ui/views/layout/grid_layout.h"
64 #include "ui/views/layout/layout_constants.h"
65 #include "ui/views/painter.h"
66 #include "ui/views/widget/widget.h"
67 #include "ui/views/window/dialog_client_view.h"
68
69 using web_modal::WebContentsModalDialogManager;
70 using web_modal::WebContentsModalDialogManagerDelegate;
71
72 namespace autofill {
73
74 namespace {
75
76 // The width for the section container.
77 const int kSectionContainerWidth = 440;
78
79 // The minimum useful height of the contents area of the dialog.
80 const int kMinimumContentsHeight = 101;
81
82 // The default height of the loading shield, also its minimum size.
83 const int kInitialLoadingShieldHeight = 150;
84
85 // Horizontal padding between text and other elements (in pixels).
86 const int kAroundTextPadding = 4;
87
88 // The space between the edges of a notification bar and the text within (in
89 // pixels).
90 const int kNotificationPadding = 17;
91
92 // Vertical padding above and below each detail section (in pixels).
93 const int kDetailSectionVerticalPadding = 10;
94
95 const int kArrowHeight = 7;
96 const int kArrowWidth = 2 * kArrowHeight;
97
98 // The padding inside the edges of the dialog, in pixels.
99 const int kDialogEdgePadding = 20;
100
101 // The vertical padding between rows of manual inputs (in pixels).
102 const int kManualInputRowPadding = 10;
103
104 // Slight shading for mouse hover and legal document background.
105 SkColor kShadingColor = SkColorSetARGB(7, 0, 0, 0);
106
107 // A border color for the legal document view.
108 SkColor kSubtleBorderColor = SkColorSetARGB(10, 0, 0, 0);
109
110 // The top and bottom padding, in pixels, for the suggestions menu dropdown
111 // arrows.
112 const int kMenuButtonTopInset = 3;
113 const int kMenuButtonBottomInset = 6;
114
115 // The height in pixels of the padding above and below the overlay message view.
116 const int kOverlayMessageVerticalPadding = 34;
117
118 // Spacing below image and above text messages in overlay view.
119 const int kOverlayImageBottomMargin = 100;
120
121 const char kNotificationAreaClassName[] = "autofill/NotificationArea";
122 const char kOverlayViewClassName[] = "autofill/OverlayView";
123 const char kSectionContainerClassName[] = "autofill/SectionContainer";
124 const char kSuggestedButtonClassName[] = "autofill/SuggestedButton";
125
126 // Draws an arrow at the top of |canvas| pointing to |tip_x|.
127 void DrawArrow(gfx::Canvas* canvas,
128                int tip_x,
129                const SkColor& fill_color,
130                const SkColor& stroke_color) {
131   const int arrow_half_width = kArrowWidth / 2.0f;
132
133   SkPath arrow;
134   arrow.moveTo(tip_x - arrow_half_width, kArrowHeight);
135   arrow.lineTo(tip_x, 0);
136   arrow.lineTo(tip_x + arrow_half_width, kArrowHeight);
137
138   SkPaint fill_paint;
139   fill_paint.setColor(fill_color);
140   canvas->DrawPath(arrow, fill_paint);
141
142   if (stroke_color != SK_ColorTRANSPARENT) {
143     SkPaint stroke_paint;
144     stroke_paint.setColor(stroke_color);
145     stroke_paint.setStyle(SkPaint::kStroke_Style);
146     canvas->DrawPath(arrow, stroke_paint);
147   }
148 }
149
150 // Returns whether |view| is an input (e.g. textfield, combobox).
151 bool IsInput(views::View* view) {
152   return view->GetClassName() == DecoratedTextfield::kViewClassName ||
153          view->GetClassName() == views::Combobox::kViewClassName;
154 }
155
156 void SelectComboboxValueOrSetToDefault(views::Combobox* combobox,
157                                        const base::string16& value) {
158   if (!combobox->SelectValue(value))
159     combobox->SetSelectedIndex(combobox->model()->GetDefaultIndex());
160 }
161
162 // This class handles layout for the first row of a SuggestionView.
163 // It exists to circumvent shortcomings of GridLayout and BoxLayout (namely that
164 // the former doesn't fully respect child visibility, and that the latter won't
165 // expand a single child).
166 class SectionRowView : public views::View {
167  public:
168   SectionRowView() { SetBorder(views::Border::CreateEmptyBorder(10, 0, 0, 0)); }
169
170   virtual ~SectionRowView() {}
171
172   // views::View implementation:
173   virtual gfx::Size GetPreferredSize() OVERRIDE {
174     int height = 0;
175     int width = 0;
176     for (int i = 0; i < child_count(); ++i) {
177       if (child_at(i)->visible()) {
178         if (width > 0)
179           width += kAroundTextPadding;
180
181         gfx::Size size = child_at(i)->GetPreferredSize();
182         height = std::max(height, size.height());
183         width += size.width();
184       }
185     }
186
187     gfx::Insets insets = GetInsets();
188     return gfx::Size(width + insets.width(), height + insets.height());
189   }
190
191   virtual void Layout() OVERRIDE {
192     const gfx::Rect bounds = GetContentsBounds();
193
194     // Icon is left aligned.
195     int start_x = bounds.x();
196     views::View* icon = child_at(0);
197     if (icon->visible()) {
198       icon->SizeToPreferredSize();
199       icon->SetX(start_x);
200       icon->SetY(bounds.y() +
201           (bounds.height() - icon->bounds().height()) / 2);
202       start_x += icon->bounds().width() + kAroundTextPadding;
203     }
204
205     // Textfield is right aligned.
206     int end_x = bounds.width();
207     views::View* decorated = child_at(2);
208     if (decorated->visible()) {
209       const int preferred_width = decorated->GetPreferredSize().width();
210       decorated->SetBounds(bounds.width() - preferred_width, bounds.y(),
211                            preferred_width, bounds.height());
212       end_x = decorated->bounds().x() - kAroundTextPadding;
213     }
214
215     // Label takes up all the space in between.
216     views::View* label = child_at(1);
217     if (label->visible())
218       label->SetBounds(start_x, bounds.y(), end_x - start_x, bounds.height());
219
220     views::View::Layout();
221   }
222
223  private:
224   DISALLOW_COPY_AND_ASSIGN(SectionRowView);
225 };
226
227 // A view that propagates visibility and preferred size changes.
228 class LayoutPropagationView : public views::View {
229  public:
230   LayoutPropagationView() {}
231   virtual ~LayoutPropagationView() {}
232
233  protected:
234   virtual void ChildVisibilityChanged(views::View* child) OVERRIDE {
235     PreferredSizeChanged();
236   }
237   virtual void ChildPreferredSizeChanged(views::View* child) OVERRIDE {
238     PreferredSizeChanged();
239   }
240
241  private:
242   DISALLOW_COPY_AND_ASSIGN(LayoutPropagationView);
243 };
244
245 // A View for a single notification banner.
246 class NotificationView : public views::View,
247                          public views::ButtonListener,
248                          public views::StyledLabelListener {
249  public:
250   NotificationView(const DialogNotification& data,
251                    AutofillDialogViewDelegate* delegate)
252       : data_(data),
253         delegate_(delegate),
254         checkbox_(NULL) {
255     scoped_ptr<views::View> label_view;
256     if (data.HasCheckbox()) {
257       scoped_ptr<views::Checkbox> checkbox(
258           new views::Checkbox(base::string16()));
259       checkbox->SetText(data.display_text());
260       checkbox->SetTextMultiLine(true);
261       checkbox->SetHorizontalAlignment(gfx::ALIGN_LEFT);
262       checkbox->SetTextColor(views::Button::STATE_NORMAL,
263                              data.GetTextColor());
264       checkbox->SetTextColor(views::Button::STATE_HOVERED,
265                              data.GetTextColor());
266       checkbox->SetChecked(data.checked());
267       checkbox->set_listener(this);
268       checkbox_ = checkbox.get();
269       label_view.reset(checkbox.release());
270     } else {
271       scoped_ptr<views::StyledLabel> label(new views::StyledLabel(
272           data.display_text(), this));
273       label->set_auto_color_readability_enabled(false);
274
275       views::StyledLabel::RangeStyleInfo text_style;
276       text_style.color = data.GetTextColor();
277
278       if (data.link_range().is_empty()) {
279         label->AddStyleRange(gfx::Range(0, data.display_text().size()),
280                              text_style);
281       } else {
282         gfx::Range prefix_range(0, data.link_range().start());
283         if (!prefix_range.is_empty())
284           label->AddStyleRange(prefix_range, text_style);
285
286         label->AddStyleRange(
287             data.link_range(),
288             views::StyledLabel::RangeStyleInfo::CreateForLink());
289
290         gfx::Range suffix_range(data.link_range().end(),
291                                 data.display_text().size());
292         if (!suffix_range.is_empty())
293           label->AddStyleRange(suffix_range, text_style);
294       }
295
296       label_view.reset(label.release());
297     }
298
299     AddChildView(label_view.release());
300
301     if (!data.tooltip_text().empty())
302       AddChildView(new TooltipIcon(data.tooltip_text()));
303
304     set_background(
305        views::Background::CreateSolidBackground(data.GetBackgroundColor()));
306     SetBorder(views::Border::CreateSolidSidedBorder(
307         1, 0, 1, 0, data.GetBorderColor()));
308   }
309
310   virtual ~NotificationView() {}
311
312   views::Checkbox* checkbox() {
313     return checkbox_;
314   }
315
316   // views::View implementation.
317   virtual gfx::Insets GetInsets() const OVERRIDE {
318     int vertical_padding = kNotificationPadding;
319     if (checkbox_)
320       vertical_padding -= 3;
321     return gfx::Insets(vertical_padding, kDialogEdgePadding,
322                        vertical_padding, kDialogEdgePadding);
323   }
324
325   virtual int GetHeightForWidth(int width) OVERRIDE {
326     int label_width = width - GetInsets().width();
327     if (child_count() > 1) {
328       views::View* tooltip_icon = child_at(1);
329       label_width -= tooltip_icon->GetPreferredSize().width() +
330           kDialogEdgePadding;
331     }
332
333     return child_at(0)->GetHeightForWidth(label_width) + GetInsets().height();
334   }
335
336   virtual void Layout() OVERRIDE {
337     // Surprisingly, GetContentsBounds() doesn't consult GetInsets().
338     gfx::Rect bounds = GetLocalBounds();
339     bounds.Inset(GetInsets());
340     int right_bound = bounds.right();
341
342     if (child_count() > 1) {
343       // The icon takes up the entire vertical space and an extra 20px on
344       // each side. This increases the hover target for the tooltip.
345       views::View* tooltip_icon = child_at(1);
346       gfx::Size icon_size = tooltip_icon->GetPreferredSize();
347       int icon_width = icon_size.width() + kDialogEdgePadding;
348       right_bound -= icon_width;
349       tooltip_icon->SetBounds(
350           right_bound, 0,
351           icon_width + kDialogEdgePadding, GetLocalBounds().height());
352     }
353
354     child_at(0)->SetBounds(bounds.x(), bounds.y(),
355                            right_bound - bounds.x(), bounds.height());
356   }
357
358   // views::ButtonListener implementation.
359   virtual void ButtonPressed(views::Button* sender,
360                              const ui::Event& event) OVERRIDE {
361     DCHECK_EQ(sender, checkbox_);
362     delegate_->NotificationCheckboxStateChanged(data_.type(),
363                                                 checkbox_->checked());
364   }
365
366   // views::StyledLabelListener implementation.
367   virtual void StyledLabelLinkClicked(const gfx::Range& range, int event_flags)
368       OVERRIDE {
369     delegate_->LinkClicked(data_.link_url());
370   }
371
372  private:
373   // The model data for this notification.
374   DialogNotification data_;
375
376   // The delegate that handles interaction with |this|.
377   AutofillDialogViewDelegate* delegate_;
378
379   // The checkbox associated with this notification, or NULL if there is none.
380   views::Checkbox* checkbox_;
381
382   DISALLOW_COPY_AND_ASSIGN(NotificationView);
383 };
384
385 // A view that displays a loading message with some dancing dots.
386 class LoadingAnimationView : public views::View,
387                              public gfx::AnimationDelegate {
388  public:
389   explicit LoadingAnimationView(const base::string16& text) :
390       container_(new views::View()) {
391     AddChildView(container_);
392     container_->SetLayoutManager(
393         new views::BoxLayout(views::BoxLayout::kHorizontal, 0, 0, 0));
394
395     const gfx::FontList& font_list =
396         ui::ResourceBundle::GetSharedInstance().GetFontList(
397             ui::ResourceBundle::LargeFont);
398     animation_.reset(new LoadingAnimation(this, font_list.GetHeight()));
399
400     container_->AddChildView(new views::Label(text, font_list));
401
402     for (size_t i = 0; i < 3; ++i) {
403       container_->AddChildView(
404           new views::Label(base::ASCIIToUTF16("."), font_list));
405     }
406
407     OnNativeThemeChanged(GetNativeTheme());
408   }
409
410   virtual ~LoadingAnimationView() {}
411
412   // views::View implementation.
413   virtual void SetVisible(bool visible) OVERRIDE {
414     if (visible)
415       animation_->Start();
416     else
417       animation_->Reset();
418
419     views::View::SetVisible(visible);
420   }
421
422   virtual void Layout() OVERRIDE {
423     gfx::Size container_size = container_->GetPreferredSize();
424     gfx::Rect container_bounds((width() - container_size.width()) / 2,
425                                (height() - container_size.height()) / 2,
426                                container_size.width(),
427                                container_size.height());
428     container_->SetBoundsRect(container_bounds);
429     container_->Layout();
430
431     for (size_t i = 0; i < 3; ++i) {
432       views::View* dot = container_->child_at(i + 1);
433       dot->SetY(dot->y() + animation_->GetCurrentValueForDot(i));
434     }
435   }
436
437   virtual void OnNativeThemeChanged(const ui::NativeTheme* theme) OVERRIDE {
438     set_background(views::Background::CreateSolidBackground(
439         theme->GetSystemColor(ui::NativeTheme::kColorId_DialogBackground)));
440   }
441
442   // gfx::AnimationDelegate implementation.
443   virtual void AnimationProgressed(const gfx::Animation* animation) OVERRIDE {
444     DCHECK_EQ(animation, animation_.get());
445     Layout();
446   }
447
448  private:
449   // Contains the "Loading" label and the dots.
450   views::View* container_;
451
452   scoped_ptr<LoadingAnimation> animation_;
453
454   DISALLOW_COPY_AND_ASSIGN(LoadingAnimationView);
455 };
456
457 }  // namespace
458
459 // AutofillDialogViews::AccountChooser -----------------------------------------
460
461 AutofillDialogViews::AccountChooser::AccountChooser(
462     AutofillDialogViewDelegate* delegate)
463     : image_(new views::ImageView()),
464       menu_button_(new views::MenuButton(NULL, base::string16(), this, true)),
465       link_(new views::Link()),
466       delegate_(delegate) {
467   SetBorder(views::Border::CreateEmptyBorder(0, 0, 0, 10));
468   SetLayoutManager(
469       new views::BoxLayout(views::BoxLayout::kHorizontal, 0, 0,
470                            kAroundTextPadding));
471   AddChildView(image_);
472
473   menu_button_->set_background(NULL);
474   menu_button_->SetBorder(views::Border::NullBorder());
475   gfx::Insets insets = GetInsets();
476   menu_button_->SetFocusPainter(
477       views::Painter::CreateDashedFocusPainterWithInsets(insets));
478   menu_button_->SetFocusable(true);
479   AddChildView(menu_button_);
480
481   link_->set_listener(this);
482   AddChildView(link_);
483 }
484
485 AutofillDialogViews::AccountChooser::~AccountChooser() {}
486
487 void AutofillDialogViews::AccountChooser::Update() {
488   SetVisible(delegate_->ShouldShowAccountChooser());
489
490   gfx::Image icon = delegate_->AccountChooserImage();
491   image_->SetImage(icon.AsImageSkia());
492   menu_button_->SetText(delegate_->AccountChooserText());
493   // This allows the button to shrink if the new text is smaller.
494   menu_button_->ClearMaxTextSize();
495
496   bool show_link = !delegate_->MenuModelForAccountChooser();
497   menu_button_->SetVisible(!show_link);
498   link_->SetText(delegate_->SignInLinkText());
499   link_->SetVisible(show_link);
500
501   menu_runner_.reset();
502
503   PreferredSizeChanged();
504 }
505
506 void AutofillDialogViews::AccountChooser::OnMenuButtonClicked(
507     views::View* source,
508     const gfx::Point& point) {
509   DCHECK_EQ(menu_button_, source);
510
511   ui::MenuModel* model = delegate_->MenuModelForAccountChooser();
512   if (!model)
513     return;
514
515   menu_runner_.reset(new views::MenuRunner(model));
516   if (menu_runner_->RunMenuAt(source->GetWidget(),
517                               NULL,
518                               source->GetBoundsInScreen(),
519                               views::MenuItemView::TOPRIGHT,
520                               ui::MENU_SOURCE_NONE,
521                               0) == views::MenuRunner::MENU_DELETED) {
522     return;
523   }
524 }
525
526 views::View* AutofillDialogViews::GetLoadingShieldForTesting() {
527   return loading_shield_;
528 }
529
530 views::WebView* AutofillDialogViews::GetSignInWebViewForTesting() {
531   return sign_in_web_view_;
532 }
533
534 views::View* AutofillDialogViews::GetNotificationAreaForTesting() {
535   return notification_area_;
536 }
537
538 views::View* AutofillDialogViews::GetScrollableAreaForTesting() {
539   return scrollable_area_;
540 }
541
542 void AutofillDialogViews::AccountChooser::LinkClicked(views::Link* source,
543                                                       int event_flags) {
544   delegate_->SignInLinkClicked();
545 }
546
547 // AutofillDialogViews::OverlayView --------------------------------------------
548
549 AutofillDialogViews::OverlayView::OverlayView(
550     AutofillDialogViewDelegate* delegate)
551     : delegate_(delegate),
552       image_view_(new views::ImageView()),
553       message_view_(new views::Label()) {
554   message_view_->SetAutoColorReadabilityEnabled(false);
555   message_view_->SetMultiLine(true);
556
557   AddChildView(image_view_);
558   AddChildView(message_view_);
559
560   OnNativeThemeChanged(GetNativeTheme());
561 }
562
563 AutofillDialogViews::OverlayView::~OverlayView() {}
564
565 int AutofillDialogViews::OverlayView::GetHeightForContentsForWidth(int width) {
566   // In this case, 0 means "no preference".
567   if (!message_view_->visible())
568     return 0;
569
570   return kOverlayImageBottomMargin +
571       views::kButtonVEdgeMarginNew +
572       message_view_->GetHeightForWidth(width) +
573       image_view_->GetHeightForWidth(width);
574 }
575
576 void AutofillDialogViews::OverlayView::UpdateState() {
577   const DialogOverlayState& state = delegate_->GetDialogOverlay();
578
579   if (state.image.IsEmpty()) {
580     SetVisible(false);
581     return;
582   }
583
584   image_view_->SetImage(state.image.ToImageSkia());
585
586   message_view_->SetVisible(!state.string.text.empty());
587   message_view_->SetText(state.string.text);
588   message_view_->SetFontList(state.string.font_list);
589   message_view_->SetEnabledColor(GetNativeTheme()->GetSystemColor(
590       ui::NativeTheme::kColorId_TextfieldReadOnlyColor));
591
592   message_view_->SetBorder(
593       views::Border::CreateEmptyBorder(kOverlayMessageVerticalPadding,
594                                        kDialogEdgePadding,
595                                        kOverlayMessageVerticalPadding,
596                                        kDialogEdgePadding));
597
598   SetVisible(true);
599 }
600
601 gfx::Insets AutofillDialogViews::OverlayView::GetInsets() const {
602   return gfx::Insets(12, 12, 12, 12);
603 }
604
605 void AutofillDialogViews::OverlayView::Layout() {
606   gfx::Rect bounds = ContentBoundsSansBubbleBorder();
607   if (!message_view_->visible()) {
608     image_view_->SetBoundsRect(bounds);
609     return;
610   }
611
612   int message_height = message_view_->GetHeightForWidth(bounds.width());
613   int y = bounds.bottom() - message_height;
614   message_view_->SetBounds(bounds.x(), y, bounds.width(), message_height);
615
616   gfx::Size image_size = image_view_->GetPreferredSize();
617   y -= image_size.height() + kOverlayImageBottomMargin;
618   image_view_->SetBounds(bounds.x(), y, bounds.width(), image_size.height());
619 }
620
621 const char* AutofillDialogViews::OverlayView::GetClassName() const {
622   return kOverlayViewClassName;
623 }
624
625 void AutofillDialogViews::OverlayView::OnPaint(gfx::Canvas* canvas) {
626   // BubbleFrameView doesn't mask the window, it just draws the border via
627   // image assets. Match that rounding here.
628   gfx::Rect rect = ContentBoundsSansBubbleBorder();
629   const SkScalar kCornerRadius = SkIntToScalar(
630       GetBubbleBorder() ? GetBubbleBorder()->GetBorderCornerRadius() : 2);
631   gfx::Path window_mask;
632   window_mask.addRoundRect(gfx::RectToSkRect(rect),
633                            kCornerRadius, kCornerRadius);
634   canvas->ClipPath(window_mask);
635
636   OnPaintBackground(canvas);
637
638   // Draw the arrow, border, and fill for the bottom area.
639   if (message_view_->visible()) {
640     const int arrow_half_width = kArrowWidth / 2.0f;
641     SkPath arrow;
642     int y = message_view_->y() - 1;
643     // Note that we purposely draw slightly outside of |rect| so that the
644     // stroke is hidden on the sides.
645     arrow.moveTo(rect.x() - 1, y);
646     arrow.rLineTo(rect.width() / 2 - arrow_half_width, 0);
647     arrow.rLineTo(arrow_half_width, -kArrowHeight);
648     arrow.rLineTo(arrow_half_width, kArrowHeight);
649     arrow.lineTo(rect.right() + 1, y);
650     arrow.lineTo(rect.right() + 1, rect.bottom() + 1);
651     arrow.lineTo(rect.x() - 1, rect.bottom() + 1);
652     arrow.close();
653
654     // The mocked alpha blends were 7 for background & 10 for the border against
655     // a very bright background. The eye perceives luminance differences of
656     // darker colors much less than lighter colors, so increase the alpha blend
657     // amount the darker the color (lower the luminance).
658     SkPaint paint;
659     SkColor background_color = background()->get_color();
660     int background_luminance =
661         color_utils::GetLuminanceForColor(background_color);
662     int background_alpha = static_cast<int>(
663         7 + 15 * (255 - background_luminance) / 255);
664     int subtle_border_alpha = static_cast<int>(
665         10 + 20 * (255 - background_luminance) / 255);
666
667     paint.setColor(color_utils::BlendTowardOppositeLuminance(
668         background_color, background_alpha));
669     paint.setStyle(SkPaint::kFill_Style);
670     canvas->DrawPath(arrow, paint);
671     paint.setColor(color_utils::BlendTowardOppositeLuminance(
672         background_color, subtle_border_alpha));
673     paint.setStyle(SkPaint::kStroke_Style);
674     canvas->DrawPath(arrow, paint);
675   }
676
677   PaintChildren(canvas);
678 }
679
680 void AutofillDialogViews::OverlayView::OnNativeThemeChanged(
681     const ui::NativeTheme* theme) {
682   set_background(views::Background::CreateSolidBackground(
683       theme->GetSystemColor(ui::NativeTheme::kColorId_DialogBackground)));
684 }
685
686 views::BubbleBorder* AutofillDialogViews::OverlayView::GetBubbleBorder() {
687   views::View* frame = GetWidget()->non_client_view()->frame_view();
688   std::string bubble_frame_view_name(views::BubbleFrameView::kViewClassName);
689   if (frame->GetClassName() == bubble_frame_view_name)
690     return static_cast<views::BubbleFrameView*>(frame)->bubble_border();
691   NOTREACHED();
692   return NULL;
693 }
694
695 gfx::Rect AutofillDialogViews::OverlayView::ContentBoundsSansBubbleBorder() {
696   gfx::Rect bounds = GetContentsBounds();
697   int bubble_width = 5;
698   if (GetBubbleBorder())
699     bubble_width = GetBubbleBorder()->GetBorderThickness();
700   bounds.Inset(bubble_width, bubble_width, bubble_width, bubble_width);
701   return bounds;
702 }
703
704 // AutofillDialogViews::NotificationArea ---------------------------------------
705
706 AutofillDialogViews::NotificationArea::NotificationArea(
707     AutofillDialogViewDelegate* delegate)
708     : delegate_(delegate) {
709   // Reserve vertical space for the arrow (regardless of whether one exists).
710   // The -1 accounts for the border.
711   SetBorder(views::Border::CreateEmptyBorder(kArrowHeight - 1, 0, 0, 0));
712
713   views::BoxLayout* box_layout =
714       new views::BoxLayout(views::BoxLayout::kVertical, 0, 0, 0);
715   SetLayoutManager(box_layout);
716 }
717
718 AutofillDialogViews::NotificationArea::~NotificationArea() {}
719
720 void AutofillDialogViews::NotificationArea::SetNotifications(
721     const std::vector<DialogNotification>& notifications) {
722   notifications_ = notifications;
723
724   RemoveAllChildViews(true);
725
726   if (notifications_.empty())
727     return;
728
729   for (size_t i = 0; i < notifications_.size(); ++i) {
730     const DialogNotification& notification = notifications_[i];
731     scoped_ptr<NotificationView> view(new NotificationView(notification,
732                                                            delegate_));
733
734     AddChildView(view.release());
735   }
736
737   PreferredSizeChanged();
738 }
739
740 gfx::Size AutofillDialogViews::NotificationArea::GetPreferredSize() {
741   gfx::Size size = views::View::GetPreferredSize();
742   // Ensure that long notifications wrap and don't enlarge the dialog.
743   size.set_width(1);
744   return size;
745 }
746
747 const char* AutofillDialogViews::NotificationArea::GetClassName() const {
748   return kNotificationAreaClassName;
749 }
750
751 void AutofillDialogViews::NotificationArea::PaintChildren(
752     gfx::Canvas* canvas) {}
753
754 void AutofillDialogViews::NotificationArea::OnPaint(gfx::Canvas* canvas) {
755   views::View::OnPaint(canvas);
756   views::View::PaintChildren(canvas);
757
758   if (HasArrow()) {
759     DrawArrow(
760         canvas,
761         GetMirroredXInView(width() - arrow_centering_anchor_->width() / 2.0f),
762         notifications_[0].GetBackgroundColor(),
763         notifications_[0].GetBorderColor());
764   }
765 }
766
767 void AutofillDialogViews::OnWidgetClosing(views::Widget* widget) {
768   observer_.Remove(widget);
769   if (error_bubble_ && error_bubble_->GetWidget() == widget)
770     error_bubble_ = NULL;
771 }
772
773 void AutofillDialogViews::OnWidgetBoundsChanged(views::Widget* widget,
774                                                 const gfx::Rect& new_bounds) {
775   // Notify the web contents of its new auto-resize limits.
776   if (sign_in_delegate_ && sign_in_web_view_->visible()) {
777     sign_in_delegate_->UpdateLimitsAndEnableAutoResize(
778         GetMinimumSignInViewSize(), GetMaximumSignInViewSize());
779   }
780   HideErrorBubble();
781 }
782
783 bool AutofillDialogViews::NotificationArea::HasArrow() {
784   return !notifications_.empty() && notifications_[0].HasArrow() &&
785       arrow_centering_anchor_.get();
786 }
787
788 // AutofillDialogViews::SectionContainer ---------------------------------------
789
790 AutofillDialogViews::SectionContainer::SectionContainer(
791     const base::string16& label,
792     views::View* controls,
793     views::Button* proxy_button)
794     : proxy_button_(proxy_button),
795       forward_mouse_events_(false) {
796   set_notify_enter_exit_on_child(true);
797
798   SetBorder(views::Border::CreateEmptyBorder(kDetailSectionVerticalPadding,
799                                              kDialogEdgePadding,
800                                              kDetailSectionVerticalPadding,
801                                              kDialogEdgePadding));
802
803   ui::ResourceBundle& rb = ui::ResourceBundle::GetSharedInstance();
804   views::Label* label_view = new views::Label(
805       label, rb.GetFontList(ui::ResourceBundle::BoldFont));
806   label_view->SetHorizontalAlignment(gfx::ALIGN_LEFT);
807
808   views::View* label_bar = new views::View();
809   views::GridLayout* label_bar_layout = new views::GridLayout(label_bar);
810   label_bar->SetLayoutManager(label_bar_layout);
811   const int kColumnSetId = 0;
812   views::ColumnSet* columns = label_bar_layout->AddColumnSet(kColumnSetId);
813   columns->AddColumn(
814       views::GridLayout::LEADING,
815       views::GridLayout::LEADING,
816       0,
817       views::GridLayout::FIXED,
818       kSectionContainerWidth - proxy_button->GetPreferredSize().width(),
819       0);
820   columns->AddColumn(views::GridLayout::LEADING,
821                      views::GridLayout::LEADING,
822                      0,
823                      views::GridLayout::USE_PREF,
824                      0,
825                      0);
826   label_bar_layout->StartRow(0, kColumnSetId);
827   label_bar_layout->AddView(label_view);
828   label_bar_layout->AddView(proxy_button);
829
830   SetLayoutManager(new views::BoxLayout(views::BoxLayout::kVertical, 0, 0, 0));
831   AddChildView(label_bar);
832   AddChildView(controls);
833 }
834
835 AutofillDialogViews::SectionContainer::~SectionContainer() {}
836
837 void AutofillDialogViews::SectionContainer::SetActive(bool active) {
838   bool is_active = active && proxy_button_->visible();
839   if (is_active == !!background())
840     return;
841
842   set_background(is_active ?
843       views::Background::CreateSolidBackground(kShadingColor) :
844       NULL);
845   SchedulePaint();
846 }
847
848 void AutofillDialogViews::SectionContainer::SetForwardMouseEvents(
849     bool forward) {
850   forward_mouse_events_ = forward;
851   if (!forward)
852     set_background(NULL);
853 }
854
855 const char* AutofillDialogViews::SectionContainer::GetClassName() const {
856   return kSectionContainerClassName;
857 }
858
859 void AutofillDialogViews::SectionContainer::OnMouseMoved(
860     const ui::MouseEvent& event) {
861   SetActive(ShouldForwardEvent(event));
862 }
863
864 void AutofillDialogViews::SectionContainer::OnMouseEntered(
865     const ui::MouseEvent& event) {
866   if (!ShouldForwardEvent(event))
867     return;
868
869   SetActive(true);
870   proxy_button_->OnMouseEntered(ProxyEvent(event));
871   SchedulePaint();
872 }
873
874 void AutofillDialogViews::SectionContainer::OnMouseExited(
875     const ui::MouseEvent& event) {
876   SetActive(false);
877   if (!ShouldForwardEvent(event))
878     return;
879
880   proxy_button_->OnMouseExited(ProxyEvent(event));
881   SchedulePaint();
882 }
883
884 bool AutofillDialogViews::SectionContainer::OnMousePressed(
885     const ui::MouseEvent& event) {
886   if (!ShouldForwardEvent(event))
887     return false;
888
889   return proxy_button_->OnMousePressed(ProxyEvent(event));
890 }
891
892 void AutofillDialogViews::SectionContainer::OnMouseReleased(
893     const ui::MouseEvent& event) {
894   if (!ShouldForwardEvent(event))
895     return;
896
897   proxy_button_->OnMouseReleased(ProxyEvent(event));
898 }
899
900 void AutofillDialogViews::SectionContainer::OnGestureEvent(
901     ui::GestureEvent* event) {
902   if (!ShouldForwardEvent(*event))
903     return;
904
905   proxy_button_->OnGestureEvent(event);
906 }
907
908 views::View* AutofillDialogViews::SectionContainer::GetEventHandlerForRect(
909     const gfx::Rect& rect) {
910   // TODO(tdanderson): Modify this function to support rect-based event
911   // targeting.
912
913   views::View* handler = views::View::GetEventHandlerForRect(rect);
914   // If the event is not in the label bar and there's no background to be
915   // cleared, let normal event handling take place.
916   if (!background() &&
917       rect.CenterPoint().y() > child_at(0)->bounds().bottom()) {
918     return handler;
919   }
920
921   // Special case for (CVC) inputs in the suggestion view.
922   if (forward_mouse_events_ &&
923       handler->GetAncestorWithClassName(DecoratedTextfield::kViewClassName)) {
924     return handler;
925   }
926
927   // Special case for the proxy button itself.
928   if (handler == proxy_button_)
929     return handler;
930
931   return this;
932 }
933
934 // static
935 ui::MouseEvent AutofillDialogViews::SectionContainer::ProxyEvent(
936     const ui::MouseEvent& event) {
937   ui::MouseEvent event_copy = event;
938   event_copy.set_location(gfx::Point());
939   return event_copy;
940 }
941
942 bool AutofillDialogViews::SectionContainer::ShouldForwardEvent(
943     const ui::LocatedEvent& event) {
944   // Always forward events on the label bar.
945   return forward_mouse_events_ || event.y() <= child_at(0)->bounds().bottom();
946 }
947
948 // AutofillDialogViews::SuggestedButton ----------------------------------------
949
950 AutofillDialogViews::SuggestedButton::SuggestedButton(
951     views::MenuButtonListener* listener)
952     : views::MenuButton(NULL, base::string16(), listener, false) {
953   const int kFocusBorderWidth = 1;
954   SetBorder(views::Border::CreateEmptyBorder(kMenuButtonTopInset,
955                                              kDialogEdgePadding,
956                                              kMenuButtonBottomInset,
957                                              kFocusBorderWidth));
958   gfx::Insets insets = GetInsets();
959   insets += gfx::Insets(-kFocusBorderWidth, -kFocusBorderWidth,
960                         -kFocusBorderWidth, -kFocusBorderWidth);
961   SetFocusPainter(
962       views::Painter::CreateDashedFocusPainterWithInsets(insets));
963   SetFocusable(true);
964 }
965
966 AutofillDialogViews::SuggestedButton::~SuggestedButton() {}
967
968 gfx::Size AutofillDialogViews::SuggestedButton::GetPreferredSize() {
969   ui::ResourceBundle& rb = ui::ResourceBundle::GetSharedInstance();
970   gfx::Size size = rb.GetImageNamed(ResourceIDForState()).Size();
971   const gfx::Insets insets = GetInsets();
972   size.Enlarge(insets.width(), insets.height());
973   return size;
974 }
975
976 const char* AutofillDialogViews::SuggestedButton::GetClassName() const {
977   return kSuggestedButtonClassName;
978 }
979
980 void AutofillDialogViews::SuggestedButton::PaintChildren(gfx::Canvas* canvas) {}
981
982 void AutofillDialogViews::SuggestedButton::OnPaint(gfx::Canvas* canvas) {
983   ui::ResourceBundle& rb = ui::ResourceBundle::GetSharedInstance();
984   const gfx::Insets insets = GetInsets();
985   canvas->DrawImageInt(*rb.GetImageSkiaNamed(ResourceIDForState()),
986                        insets.left(), insets.top());
987   views::Painter::PaintFocusPainter(this, canvas, focus_painter());
988 }
989
990 int AutofillDialogViews::SuggestedButton::ResourceIDForState() const {
991   views::Button::ButtonState button_state = state();
992   if (button_state == views::Button::STATE_PRESSED)
993     return IDR_AUTOFILL_DIALOG_MENU_BUTTON_P;
994   else if (button_state == views::Button::STATE_HOVERED)
995     return IDR_AUTOFILL_DIALOG_MENU_BUTTON_H;
996   else if (button_state == views::Button::STATE_DISABLED)
997     return IDR_AUTOFILL_DIALOG_MENU_BUTTON_D;
998   DCHECK_EQ(views::Button::STATE_NORMAL, button_state);
999   return IDR_AUTOFILL_DIALOG_MENU_BUTTON;
1000 }
1001
1002 // AutofillDialogViews::DetailsContainerView -----------------------------------
1003
1004 AutofillDialogViews::DetailsContainerView::DetailsContainerView(
1005     const base::Closure& callback)
1006     : bounds_changed_callback_(callback),
1007       ignore_layouts_(false) {}
1008
1009 AutofillDialogViews::DetailsContainerView::~DetailsContainerView() {}
1010
1011 void AutofillDialogViews::DetailsContainerView::OnBoundsChanged(
1012     const gfx::Rect& previous_bounds) {
1013   bounds_changed_callback_.Run();
1014 }
1015
1016 void AutofillDialogViews::DetailsContainerView::Layout() {
1017   if (!ignore_layouts_)
1018     views::View::Layout();
1019 }
1020
1021 // AutofillDialogViews::SuggestionView -----------------------------------------
1022
1023 AutofillDialogViews::SuggestionView::SuggestionView(
1024     AutofillDialogViews* autofill_dialog)
1025     : label_(new views::Label()),
1026       label_line_2_(new views::Label()),
1027       icon_(new views::ImageView()),
1028       decorated_(
1029           new DecoratedTextfield(base::string16(),
1030                                  base::string16(),
1031                                  autofill_dialog)) {
1032   // TODO(estade): Make this the correct color.
1033   SetBorder(views::Border::CreateSolidSidedBorder(1, 0, 0, 0, SK_ColorLTGRAY));
1034
1035   SectionRowView* label_container = new SectionRowView();
1036   AddChildView(label_container);
1037
1038   // Label and icon.
1039   label_container->AddChildView(icon_);
1040   label_->SetHorizontalAlignment(gfx::ALIGN_LEFT);
1041   label_container->AddChildView(label_);
1042
1043   // TODO(estade): get the sizing and spacing right on this textfield.
1044   decorated_->SetVisible(false);
1045   decorated_->set_default_width_in_chars(15);
1046   label_container->AddChildView(decorated_);
1047
1048   label_line_2_->SetHorizontalAlignment(gfx::ALIGN_LEFT);
1049   label_line_2_->SetVisible(false);
1050   label_line_2_->SetLineHeight(22);
1051   label_line_2_->SetMultiLine(true);
1052   AddChildView(label_line_2_);
1053
1054   SetLayoutManager(new views::BoxLayout(views::BoxLayout::kVertical, 0, 0, 7));
1055 }
1056
1057 AutofillDialogViews::SuggestionView::~SuggestionView() {}
1058
1059 gfx::Size AutofillDialogViews::SuggestionView::GetPreferredSize() {
1060   // There's no preferred width. The parent's layout should get the preferred
1061   // height from GetHeightForWidth().
1062   return gfx::Size();
1063 }
1064
1065 int AutofillDialogViews::SuggestionView::GetHeightForWidth(int width) {
1066   int height = 0;
1067   CanUseVerticallyCompactText(width, &height);
1068   return height;
1069 }
1070
1071 bool AutofillDialogViews::SuggestionView::CanUseVerticallyCompactText(
1072     int available_width,
1073     int* resulting_height) {
1074   // This calculation may be costly, avoid doing it more than once per width.
1075   if (!calculated_heights_.count(available_width)) {
1076     // Changing the state of |this| now will lead to extra layouts and
1077     // paints we don't want, so create another SuggestionView to calculate
1078     // which label we have room to show.
1079     SuggestionView sizing_view(NULL);
1080     sizing_view.SetLabelText(state_.vertically_compact_text);
1081     sizing_view.SetIcon(state_.icon);
1082     sizing_view.SetTextfield(state_.extra_text, state_.extra_icon);
1083     sizing_view.label_->SetSize(gfx::Size(available_width, 0));
1084     sizing_view.label_line_2_->SetSize(gfx::Size(available_width, 0));
1085
1086     // Shortcut |sizing_view|'s GetHeightForWidth() to avoid an infinite loop.
1087     // Its BoxLayout must do these calculations for us.
1088     views::LayoutManager* layout = sizing_view.GetLayoutManager();
1089     if (layout->GetPreferredSize(&sizing_view).width() <= available_width) {
1090       calculated_heights_[available_width] = std::make_pair(
1091           true,
1092           layout->GetPreferredHeightForWidth(&sizing_view, available_width));
1093     } else {
1094       sizing_view.SetLabelText(state_.horizontally_compact_text);
1095       calculated_heights_[available_width] = std::make_pair(
1096           false,
1097           layout->GetPreferredHeightForWidth(&sizing_view, available_width));
1098     }
1099   }
1100
1101   const std::pair<bool, int>& values = calculated_heights_[available_width];
1102   *resulting_height = values.second;
1103   return values.first;
1104 }
1105
1106 void AutofillDialogViews::SuggestionView::OnBoundsChanged(
1107     const gfx::Rect& previous_bounds) {
1108   UpdateLabelText();
1109 }
1110
1111 void AutofillDialogViews::SuggestionView::SetState(
1112     const SuggestionState& state) {
1113   calculated_heights_.clear();
1114   state_ = state;
1115   SetVisible(state_.visible);
1116   UpdateLabelText();
1117   SetIcon(state_.icon);
1118   SetTextfield(state_.extra_text, state_.extra_icon);
1119   PreferredSizeChanged();
1120 }
1121
1122 void AutofillDialogViews::SuggestionView::SetLabelText(
1123     const base::string16& text) {
1124   // TODO(estade): does this localize well?
1125   base::string16 line_return(base::ASCIIToUTF16("\n"));
1126   size_t position = text.find(line_return);
1127   if (position == base::string16::npos) {
1128     label_->SetText(text);
1129     label_line_2_->SetVisible(false);
1130   } else {
1131     label_->SetText(text.substr(0, position));
1132     label_line_2_->SetText(text.substr(position + line_return.length()));
1133     label_line_2_->SetVisible(true);
1134   }
1135 }
1136
1137 void AutofillDialogViews::SuggestionView::SetIcon(
1138     const gfx::Image& image) {
1139   icon_->SetVisible(!image.IsEmpty());
1140   icon_->SetImage(image.AsImageSkia());
1141 }
1142
1143 void AutofillDialogViews::SuggestionView::SetTextfield(
1144     const base::string16& placeholder_text,
1145     const gfx::Image& icon) {
1146   decorated_->set_placeholder_text(placeholder_text);
1147   decorated_->SetIcon(icon);
1148   decorated_->SetVisible(!placeholder_text.empty());
1149 }
1150
1151 void AutofillDialogViews::SuggestionView::UpdateLabelText() {
1152   int unused;
1153   SetLabelText(CanUseVerticallyCompactText(width(), &unused) ?
1154       state_.vertically_compact_text :
1155       state_.horizontally_compact_text);
1156 }
1157
1158 // AutofillDialogView ----------------------------------------------------------
1159
1160 // static
1161 AutofillDialogView* AutofillDialogView::Create(
1162     AutofillDialogViewDelegate* delegate) {
1163   return new AutofillDialogViews(delegate);
1164 }
1165
1166 // AutofillDialogViews ---------------------------------------------------------
1167
1168 AutofillDialogViews::AutofillDialogViews(AutofillDialogViewDelegate* delegate)
1169     : delegate_(delegate),
1170       updates_scope_(0),
1171       needs_update_(false),
1172       window_(NULL),
1173       notification_area_(NULL),
1174       account_chooser_(NULL),
1175       sign_in_web_view_(NULL),
1176       scrollable_area_(NULL),
1177       details_container_(NULL),
1178       loading_shield_(NULL),
1179       loading_shield_height_(0),
1180       overlay_view_(NULL),
1181       button_strip_extra_view_(NULL),
1182       save_in_chrome_checkbox_(NULL),
1183       save_in_chrome_checkbox_container_(NULL),
1184       button_strip_image_(NULL),
1185       footnote_view_(NULL),
1186       legal_document_view_(NULL),
1187       focus_manager_(NULL),
1188       error_bubble_(NULL),
1189       observer_(this) {
1190   DCHECK(delegate);
1191   detail_groups_.insert(std::make_pair(SECTION_CC,
1192                                        DetailsGroup(SECTION_CC)));
1193   detail_groups_.insert(std::make_pair(SECTION_BILLING,
1194                                        DetailsGroup(SECTION_BILLING)));
1195   detail_groups_.insert(std::make_pair(SECTION_CC_BILLING,
1196                                        DetailsGroup(SECTION_CC_BILLING)));
1197   detail_groups_.insert(std::make_pair(SECTION_SHIPPING,
1198                                        DetailsGroup(SECTION_SHIPPING)));
1199 }
1200
1201 AutofillDialogViews::~AutofillDialogViews() {
1202   HideErrorBubble();
1203   DCHECK(!window_);
1204 }
1205
1206 void AutofillDialogViews::Show() {
1207   InitChildViews();
1208   UpdateAccountChooser();
1209   UpdateNotificationArea();
1210   UpdateButtonStripExtraView();
1211
1212   // Ownership of |contents_| is handed off by this call. The widget will take
1213   // care of deleting itself after calling DeleteDelegate().
1214   WebContentsModalDialogManager* web_contents_modal_dialog_manager =
1215       WebContentsModalDialogManager::FromWebContents(
1216           delegate_->GetWebContents());
1217   WebContentsModalDialogManagerDelegate* modal_delegate =
1218       web_contents_modal_dialog_manager->delegate();
1219   DCHECK(modal_delegate);
1220   window_ = views::Widget::CreateWindowAsFramelessChild(
1221       this, modal_delegate->GetWebContentsModalDialogHost()->GetHostView());
1222   web_contents_modal_dialog_manager->ShowDialog(window_->GetNativeView());
1223   focus_manager_ = window_->GetFocusManager();
1224   focus_manager_->AddFocusChangeListener(this);
1225
1226   ShowDialogInMode(DETAIL_INPUT);
1227
1228   // Listen for size changes on the browser.
1229   views::Widget* browser_widget =
1230       views::Widget::GetTopLevelWidgetForNativeView(
1231           delegate_->GetWebContents()->GetView()->GetNativeView());
1232   observer_.Add(browser_widget);
1233 }
1234
1235 void AutofillDialogViews::Hide() {
1236   if (window_)
1237     window_->Close();
1238 }
1239
1240 void AutofillDialogViews::UpdatesStarted() {
1241   updates_scope_++;
1242 }
1243
1244 void AutofillDialogViews::UpdatesFinished() {
1245   updates_scope_--;
1246   DCHECK_GE(updates_scope_, 0);
1247   if (updates_scope_ == 0 && needs_update_) {
1248     needs_update_ = false;
1249     ContentsPreferredSizeChanged();
1250   }
1251 }
1252
1253 void AutofillDialogViews::UpdateAccountChooser() {
1254   account_chooser_->Update();
1255
1256   bool show_loading = delegate_->ShouldShowSpinner();
1257   if (show_loading != loading_shield_->visible()) {
1258     if (show_loading) {
1259       loading_shield_height_ = std::max(kInitialLoadingShieldHeight,
1260                                         GetContentsBounds().height());
1261       ShowDialogInMode(LOADING);
1262     } else {
1263       bool show_sign_in = delegate_->ShouldShowSignInWebView();
1264       ShowDialogInMode(show_sign_in ? SIGN_IN : DETAIL_INPUT);
1265     }
1266
1267     InvalidateLayout();
1268     ContentsPreferredSizeChanged();
1269   }
1270
1271   // Update legal documents for the account.
1272   if (footnote_view_) {
1273     const base::string16 text = delegate_->LegalDocumentsText();
1274     legal_document_view_->SetText(text);
1275
1276     if (!text.empty()) {
1277       const std::vector<gfx::Range>& link_ranges =
1278           delegate_->LegalDocumentLinks();
1279       for (size_t i = 0; i < link_ranges.size(); ++i) {
1280         views::StyledLabel::RangeStyleInfo link_range_info =
1281             views::StyledLabel::RangeStyleInfo::CreateForLink();
1282         link_range_info.disable_line_wrapping = false;
1283         legal_document_view_->AddStyleRange(link_ranges[i], link_range_info);
1284       }
1285     }
1286
1287     footnote_view_->SetVisible(!text.empty());
1288     ContentsPreferredSizeChanged();
1289   }
1290
1291   if (GetWidget())
1292     GetWidget()->UpdateWindowTitle();
1293 }
1294
1295 void AutofillDialogViews::UpdateButtonStrip() {
1296   button_strip_extra_view_->SetVisible(
1297       GetDialogButtons() != ui::DIALOG_BUTTON_NONE);
1298   UpdateButtonStripExtraView();
1299   GetDialogClientView()->UpdateDialogButtons();
1300
1301   ContentsPreferredSizeChanged();
1302 }
1303
1304 void AutofillDialogViews::UpdateOverlay() {
1305   overlay_view_->UpdateState();
1306   ContentsPreferredSizeChanged();
1307 }
1308
1309 void AutofillDialogViews::UpdateDetailArea() {
1310   scrollable_area_->SetVisible(true);
1311   ContentsPreferredSizeChanged();
1312 }
1313
1314 void AutofillDialogViews::UpdateForErrors() {
1315   ValidateForm();
1316 }
1317
1318 void AutofillDialogViews::UpdateNotificationArea() {
1319   DCHECK(notification_area_);
1320   notification_area_->SetNotifications(delegate_->CurrentNotifications());
1321   ContentsPreferredSizeChanged();
1322 }
1323
1324 void AutofillDialogViews::UpdateSection(DialogSection section) {
1325   UpdateSectionImpl(section, true);
1326 }
1327
1328 void AutofillDialogViews::UpdateErrorBubble() {
1329   if (!delegate_->ShouldShowErrorBubble())
1330     HideErrorBubble();
1331 }
1332
1333 void AutofillDialogViews::FillSection(DialogSection section,
1334                                       ServerFieldType originating_type) {
1335   DetailsGroup* group = GroupForSection(section);
1336   // Make sure to overwrite the originating input if it exists.
1337   TextfieldMap::iterator text_mapping =
1338       group->textfields.find(originating_type);
1339   if (text_mapping != group->textfields.end())
1340     text_mapping->second->SetText(base::string16());
1341
1342   // If the Autofill data comes from a credit card, make sure to overwrite the
1343   // CC comboboxes (even if they already have something in them). If the
1344   // Autofill data comes from an AutofillProfile, leave the comboboxes alone.
1345   if (section == GetCreditCardSection() &&
1346       AutofillType(originating_type).group() == CREDIT_CARD) {
1347     for (ComboboxMap::const_iterator it = group->comboboxes.begin();
1348          it != group->comboboxes.end(); ++it) {
1349       if (AutofillType(it->first).group() == CREDIT_CARD)
1350         it->second->SetSelectedIndex(it->second->model()->GetDefaultIndex());
1351     }
1352   }
1353
1354   UpdateSectionImpl(section, false);
1355 }
1356
1357 void AutofillDialogViews::GetUserInput(DialogSection section,
1358                                        FieldValueMap* output) {
1359   DetailsGroup* group = GroupForSection(section);
1360   for (TextfieldMap::const_iterator it = group->textfields.begin();
1361        it != group->textfields.end(); ++it) {
1362     output->insert(std::make_pair(it->first, it->second->text()));
1363   }
1364   for (ComboboxMap::const_iterator it = group->comboboxes.begin();
1365        it != group->comboboxes.end(); ++it) {
1366     output->insert(std::make_pair(it->first,
1367         it->second->model()->GetItemAt(it->second->selected_index())));
1368   }
1369 }
1370
1371 base::string16 AutofillDialogViews::GetCvc() {
1372   return GroupForSection(GetCreditCardSection())->suggested_info->
1373       decorated_textfield()->text();
1374 }
1375
1376 bool AutofillDialogViews::HitTestInput(ServerFieldType type,
1377                                        const gfx::Point& screen_point) {
1378   views::View* view = TextfieldForType(type);
1379   if (!view)
1380     view = ComboboxForType(type);
1381
1382   if (view) {
1383     gfx::Point target_point(screen_point);
1384     views::View::ConvertPointFromScreen(view, &target_point);
1385     return view->HitTestPoint(target_point);
1386   }
1387
1388   NOTREACHED();
1389   return false;
1390 }
1391
1392 bool AutofillDialogViews::SaveDetailsLocally() {
1393   DCHECK(save_in_chrome_checkbox_->visible());
1394   return save_in_chrome_checkbox_->checked();
1395 }
1396
1397 const content::NavigationController* AutofillDialogViews::ShowSignIn() {
1398   // The initial minimum width and height are set such that the dialog
1399   // won't change size before the page is loaded.
1400   int min_width = GetContentsBounds().width();
1401   // The height has to include the button strip.
1402   int min_height = GetDialogClientView()->GetContentsBounds().height();
1403
1404   // TODO(abodenha): We should be able to use the WebContents of the WebView
1405   // to navigate instead of LoadInitialURL. Figure out why it doesn't work.
1406   sign_in_delegate_.reset(
1407       new AutofillDialogSignInDelegate(
1408           this,
1409           sign_in_web_view_->GetWebContents(),
1410           delegate_->GetWebContents(),
1411           gfx::Size(min_width, min_height), GetMaximumSignInViewSize()));
1412   sign_in_web_view_->LoadInitialURL(delegate_->SignInUrl());
1413
1414   ShowDialogInMode(SIGN_IN);
1415
1416   ContentsPreferredSizeChanged();
1417
1418   return &sign_in_web_view_->web_contents()->GetController();
1419 }
1420
1421 void AutofillDialogViews::HideSignIn() {
1422   sign_in_web_view_->SetWebContents(NULL);
1423
1424   if (delegate_->ShouldShowSpinner()) {
1425     UpdateAccountChooser();
1426   } else {
1427     ShowDialogInMode(DETAIL_INPUT);
1428     InvalidateLayout();
1429   }
1430   DCHECK(!sign_in_web_view_->visible());
1431
1432   ContentsPreferredSizeChanged();
1433 }
1434
1435 void AutofillDialogViews::ModelChanged() {
1436   menu_runner_.reset();
1437
1438   for (DetailGroupMap::const_iterator iter = detail_groups_.begin();
1439        iter != detail_groups_.end(); ++iter) {
1440     UpdateDetailsGroupState(iter->second);
1441   }
1442 }
1443
1444 void AutofillDialogViews::OnSignInResize(const gfx::Size& pref_size) {
1445   sign_in_web_view_->SetPreferredSize(pref_size);
1446   ContentsPreferredSizeChanged();
1447 }
1448
1449 void AutofillDialogViews::ValidateSection(DialogSection section) {
1450   ValidateGroup(*GroupForSection(section), VALIDATE_EDIT);
1451 }
1452
1453 gfx::Size AutofillDialogViews::GetPreferredSize() {
1454   if (preferred_size_.IsEmpty())
1455     preferred_size_ = CalculatePreferredSize(false);
1456
1457   return preferred_size_;
1458 }
1459
1460 gfx::Size AutofillDialogViews::GetMinimumSize() {
1461   return CalculatePreferredSize(true);
1462 }
1463
1464 void AutofillDialogViews::Layout() {
1465   const gfx::Rect content_bounds = GetContentsBounds();
1466   if (sign_in_web_view_->visible()) {
1467     sign_in_web_view_->SetBoundsRect(content_bounds);
1468     return;
1469   }
1470
1471   if (loading_shield_->visible()) {
1472     loading_shield_->SetBoundsRect(bounds());
1473     return;
1474   }
1475
1476   const int x = content_bounds.x();
1477   const int y = content_bounds.y();
1478   const int width = content_bounds.width();
1479   // Layout notification area at top of dialog.
1480   int notification_height = notification_area_->GetHeightForWidth(width);
1481   notification_area_->SetBounds(x, y, width, notification_height);
1482
1483   // The rest (the |scrollable_area_|) takes up whatever's left.
1484   if (scrollable_area_->visible()) {
1485     int scroll_y = y;
1486     if (notification_height > notification_area_->GetInsets().height())
1487       scroll_y += notification_height + views::kRelatedControlVerticalSpacing;
1488
1489     int scroll_bottom = content_bounds.bottom();
1490     DCHECK_EQ(scrollable_area_->contents(), details_container_);
1491     details_container_->SizeToPreferredSize();
1492     details_container_->Layout();
1493     // TODO(estade): remove this hack. See crbug.com/285996
1494     details_container_->set_ignore_layouts(true);
1495     scrollable_area_->SetBounds(x, scroll_y, width, scroll_bottom - scroll_y);
1496     details_container_->set_ignore_layouts(false);
1497   }
1498
1499   if (error_bubble_)
1500     error_bubble_->UpdatePosition();
1501 }
1502
1503 void AutofillDialogViews::OnNativeThemeChanged(
1504     const ui::NativeTheme* theme) {
1505   if (!legal_document_view_)
1506     return;
1507
1508   // NOTE: This color may change because of |auto_color_readability|, set on
1509   // |legal_document_view_|.
1510   views::StyledLabel::RangeStyleInfo default_style;
1511   default_style.color =
1512       theme->GetSystemColor(ui::NativeTheme::kColorId_LabelDisabledColor);
1513
1514   legal_document_view_->SetDefaultStyle(default_style);
1515 }
1516
1517 base::string16 AutofillDialogViews::GetWindowTitle() const {
1518   base::string16 title = delegate_->DialogTitle();
1519   // Hack alert: we don't want the dialog to jiggle when a title is added or
1520   // removed. Setting a non-empty string here keeps the dialog's title bar the
1521   // same size.
1522   return title.empty() ? base::ASCIIToUTF16(" ") : title;
1523 }
1524
1525 void AutofillDialogViews::WindowClosing() {
1526   focus_manager_->RemoveFocusChangeListener(this);
1527 }
1528
1529 void AutofillDialogViews::DeleteDelegate() {
1530   window_ = NULL;
1531   // |this| belongs to the controller (|delegate_|).
1532   delegate_->ViewClosed();
1533 }
1534
1535 int AutofillDialogViews::GetDialogButtons() const {
1536   return delegate_->GetDialogButtons();
1537 }
1538
1539 int AutofillDialogViews::GetDefaultDialogButton() const {
1540   if (GetDialogButtons() & ui::DIALOG_BUTTON_OK)
1541     return ui::DIALOG_BUTTON_OK;
1542
1543   return ui::DIALOG_BUTTON_NONE;
1544 }
1545
1546 base::string16 AutofillDialogViews::GetDialogButtonLabel(
1547     ui::DialogButton button) const {
1548   return button == ui::DIALOG_BUTTON_OK ?
1549       delegate_->ConfirmButtonText() : delegate_->CancelButtonText();
1550 }
1551
1552 bool AutofillDialogViews::ShouldDefaultButtonBeBlue() const {
1553   return true;
1554 }
1555
1556 bool AutofillDialogViews::IsDialogButtonEnabled(ui::DialogButton button) const {
1557   return delegate_->IsDialogButtonEnabled(button);
1558 }
1559
1560 views::View* AutofillDialogViews::GetInitiallyFocusedView() {
1561   if (!window_ || !focus_manager_)
1562     return NULL;
1563
1564   if (sign_in_web_view_->visible())
1565     return sign_in_web_view_;
1566
1567   if (loading_shield_->visible())
1568     return views::DialogDelegateView::GetInitiallyFocusedView();
1569
1570   DCHECK(scrollable_area_->visible());
1571
1572   views::FocusManager* manager = focus_manager_;
1573   for (views::View* next = scrollable_area_;
1574        next;
1575        next = manager->GetNextFocusableView(next, window_, false, true)) {
1576     if (!IsInput(next))
1577       continue;
1578
1579     // If there are no invalid inputs, return the first input found. Otherwise,
1580     // return the first invalid input found.
1581     if (validity_map_.empty() ||
1582         validity_map_.find(next) != validity_map_.end()) {
1583       return next;
1584     }
1585   }
1586
1587   return views::DialogDelegateView::GetInitiallyFocusedView();
1588 }
1589
1590 views::View* AutofillDialogViews::CreateExtraView() {
1591   return button_strip_extra_view_;
1592 }
1593
1594 views::View* AutofillDialogViews::CreateTitlebarExtraView() {
1595   return account_chooser_;
1596 }
1597
1598 views::View* AutofillDialogViews::CreateFootnoteView() {
1599   footnote_view_ = new LayoutPropagationView();
1600   footnote_view_->SetLayoutManager(
1601       new views::BoxLayout(views::BoxLayout::kVertical,
1602                            kDialogEdgePadding,
1603                            kDialogEdgePadding,
1604                            0));
1605   footnote_view_->SetBorder(
1606       views::Border::CreateSolidSidedBorder(1, 0, 0, 0, kSubtleBorderColor));
1607   footnote_view_->set_background(
1608       views::Background::CreateSolidBackground(kShadingColor));
1609
1610   legal_document_view_ = new views::StyledLabel(base::string16(), this);
1611   OnNativeThemeChanged(GetNativeTheme());
1612
1613   footnote_view_->AddChildView(legal_document_view_);
1614   footnote_view_->SetVisible(false);
1615
1616   return footnote_view_;
1617 }
1618
1619 views::View* AutofillDialogViews::CreateOverlayView() {
1620   return overlay_view_;
1621 }
1622
1623 bool AutofillDialogViews::Cancel() {
1624   return delegate_->OnCancel();
1625 }
1626
1627 bool AutofillDialogViews::Accept() {
1628   if (ValidateForm())
1629     return delegate_->OnAccept();
1630
1631   // |ValidateForm()| failed; there should be invalid views in |validity_map_|.
1632   DCHECK(!validity_map_.empty());
1633   FocusInitialView();
1634
1635   return false;
1636 }
1637
1638 void AutofillDialogViews::ContentsChanged(views::Textfield* sender,
1639                                           const base::string16& new_contents) {
1640   InputEditedOrActivated(TypeForTextfield(sender),
1641                          sender->GetBoundsInScreen(),
1642                          true);
1643 }
1644
1645 bool AutofillDialogViews::HandleKeyEvent(views::Textfield* sender,
1646                                          const ui::KeyEvent& key_event) {
1647   ui::KeyEvent copy(key_event);
1648   content::NativeWebKeyboardEvent event(&copy);
1649   return delegate_->HandleKeyPressEventInInput(event);
1650 }
1651
1652 bool AutofillDialogViews::HandleMouseEvent(views::Textfield* sender,
1653                                            const ui::MouseEvent& mouse_event) {
1654   if (mouse_event.IsLeftMouseButton() && sender->HasFocus()) {
1655     InputEditedOrActivated(TypeForTextfield(sender),
1656                            sender->GetBoundsInScreen(),
1657                            false);
1658     // Show an error bubble if a user clicks on an input that's already focused
1659     // (and invalid).
1660     ShowErrorBubbleForViewIfNecessary(sender);
1661   }
1662
1663   return false;
1664 }
1665
1666 void AutofillDialogViews::OnWillChangeFocus(
1667     views::View* focused_before,
1668     views::View* focused_now) {
1669   delegate_->FocusMoved();
1670   HideErrorBubble();
1671 }
1672
1673 void AutofillDialogViews::OnDidChangeFocus(
1674     views::View* focused_before,
1675     views::View* focused_now) {
1676   // If user leaves an edit-field, revalidate the group it belongs to.
1677   if (focused_before) {
1678     DetailsGroup* group = GroupForView(focused_before);
1679     if (group && group->container->visible())
1680       ValidateGroup(*group, VALIDATE_EDIT);
1681   }
1682
1683   // Show an error bubble when the user focuses the input.
1684   if (focused_now) {
1685     focused_now->ScrollRectToVisible(focused_now->GetLocalBounds());
1686     ShowErrorBubbleForViewIfNecessary(focused_now);
1687   }
1688 }
1689
1690 void AutofillDialogViews::OnPerformAction(views::Combobox* combobox) {
1691   DialogSection section = GroupForView(combobox)->section;
1692   InputEditedOrActivated(TypeForCombobox(combobox), gfx::Rect(), true);
1693   // NOTE: |combobox| may have been deleted.
1694   ValidateGroup(*GroupForSection(section), VALIDATE_EDIT);
1695   SetEditabilityForSection(section);
1696 }
1697
1698 void AutofillDialogViews::StyledLabelLinkClicked(const gfx::Range& range,
1699                                                  int event_flags) {
1700   delegate_->LegalDocumentLinkClicked(range);
1701 }
1702
1703 void AutofillDialogViews::OnMenuButtonClicked(views::View* source,
1704                                               const gfx::Point& point) {
1705   DCHECK_EQ(kSuggestedButtonClassName, source->GetClassName());
1706
1707   DetailsGroup* group = NULL;
1708   for (DetailGroupMap::iterator iter = detail_groups_.begin();
1709        iter != detail_groups_.end(); ++iter) {
1710     if (source == iter->second.suggested_button) {
1711       group = &iter->second;
1712       break;
1713     }
1714   }
1715   DCHECK(group);
1716
1717   if (!group->suggested_button->visible())
1718     return;
1719
1720   menu_runner_.reset(new views::MenuRunner(
1721                          delegate_->MenuModelForSection(group->section)));
1722
1723   group->container->SetActive(true);
1724   views::Button::ButtonState state = group->suggested_button->state();
1725   group->suggested_button->SetState(views::Button::STATE_PRESSED);
1726
1727   gfx::Rect screen_bounds = source->GetBoundsInScreen();
1728   screen_bounds.Inset(source->GetInsets());
1729   if (menu_runner_->RunMenuAt(source->GetWidget(),
1730                               NULL,
1731                               screen_bounds,
1732                               views::MenuItemView::TOPRIGHT,
1733                               ui::MENU_SOURCE_NONE,
1734                               0) == views::MenuRunner::MENU_DELETED) {
1735     return;
1736   }
1737
1738   group->container->SetActive(false);
1739   group->suggested_button->SetState(state);
1740 }
1741
1742 gfx::Size AutofillDialogViews::CalculatePreferredSize(bool get_minimum_size) {
1743   gfx::Insets insets = GetInsets();
1744   gfx::Size scroll_size = scrollable_area_->contents()->GetPreferredSize();
1745   // The width is always set by the scroll area.
1746   const int width = scroll_size.width();
1747
1748   if (sign_in_web_view_->visible()) {
1749     const gfx::Size size = static_cast<views::View*>(sign_in_web_view_)->
1750         GetPreferredSize();
1751     return gfx::Size(width + insets.width(), size.height() + insets.height());
1752   }
1753
1754   if (overlay_view_->visible()) {
1755     const int height = overlay_view_->GetHeightForContentsForWidth(width);
1756     if (height != 0)
1757       return gfx::Size(width + insets.width(), height + insets.height());
1758   }
1759
1760   if (loading_shield_->visible()) {
1761     return gfx::Size(width + insets.width(),
1762                      loading_shield_height_ + insets.height());
1763   }
1764
1765   int height = 0;
1766   const int notification_height = notification_area_->GetHeightForWidth(width);
1767   if (notification_height > notification_area_->GetInsets().height())
1768     height += notification_height + views::kRelatedControlVerticalSpacing;
1769
1770   if (scrollable_area_->visible())
1771     height += get_minimum_size ? kMinimumContentsHeight : scroll_size.height();
1772
1773   return gfx::Size(width + insets.width(), height + insets.height());
1774 }
1775
1776 gfx::Size AutofillDialogViews::GetMinimumSignInViewSize() const {
1777   return gfx::Size(GetDialogClientView()->size().width() - GetInsets().width(),
1778                    kMinimumContentsHeight);
1779 }
1780
1781 gfx::Size AutofillDialogViews::GetMaximumSignInViewSize() const {
1782   web_modal::WebContentsModalDialogHost* dialog_host =
1783       WebContentsModalDialogManager::FromWebContents(
1784           delegate_->GetWebContents())->delegate()->
1785               GetWebContentsModalDialogHost();
1786
1787   // Inset the maximum dialog height to get the maximum content height.
1788   int height = dialog_host->GetMaximumDialogSize().height();
1789   const int non_client_height = GetWidget()->non_client_view()->height();
1790   const int client_height = GetWidget()->client_view()->height();
1791   // TODO(msw): Resolve the 12 pixel discrepancy; is that the bubble border?
1792   height -= non_client_height - client_height - 12;
1793   height = std::max(height, kMinimumContentsHeight);
1794
1795   // The dialog's width never changes.
1796   const int width = GetDialogClientView()->size().width() - GetInsets().width();
1797   return gfx::Size(width, height);
1798 }
1799
1800 DialogSection AutofillDialogViews::GetCreditCardSection() const {
1801   if (delegate_->SectionIsActive(SECTION_CC))
1802     return SECTION_CC;
1803
1804   DCHECK(delegate_->SectionIsActive(SECTION_CC_BILLING));
1805   return SECTION_CC_BILLING;
1806 }
1807
1808 void AutofillDialogViews::InitChildViews() {
1809   button_strip_extra_view_ = new LayoutPropagationView();
1810   button_strip_extra_view_->SetLayoutManager(
1811       new views::BoxLayout(views::BoxLayout::kHorizontal, 0, 0, 0));
1812
1813   save_in_chrome_checkbox_container_ = new views::View();
1814   save_in_chrome_checkbox_container_->SetLayoutManager(
1815       new views::BoxLayout(views::BoxLayout::kHorizontal, 0, 0, 7));
1816   button_strip_extra_view_->AddChildView(save_in_chrome_checkbox_container_);
1817
1818   save_in_chrome_checkbox_ =
1819       new views::Checkbox(delegate_->SaveLocallyText());
1820   save_in_chrome_checkbox_->SetChecked(delegate_->ShouldSaveInChrome());
1821   save_in_chrome_checkbox_container_->AddChildView(save_in_chrome_checkbox_);
1822
1823   save_in_chrome_checkbox_container_->AddChildView(
1824       new TooltipIcon(delegate_->SaveLocallyTooltip()));
1825
1826   button_strip_image_ = new views::ImageView();
1827   button_strip_extra_view_->AddChildView(button_strip_image_);
1828
1829   account_chooser_ = new AccountChooser(delegate_);
1830   notification_area_ = new NotificationArea(delegate_);
1831   notification_area_->set_arrow_centering_anchor(account_chooser_->AsWeakPtr());
1832   AddChildView(notification_area_);
1833
1834   scrollable_area_ = new views::ScrollView();
1835   scrollable_area_->set_hide_horizontal_scrollbar(true);
1836   scrollable_area_->SetContents(CreateDetailsContainer());
1837   AddChildView(scrollable_area_);
1838
1839   loading_shield_ = new LoadingAnimationView(delegate_->SpinnerText());
1840   AddChildView(loading_shield_);
1841
1842   sign_in_web_view_ = new views::WebView(delegate_->profile());
1843   AddChildView(sign_in_web_view_);
1844
1845   overlay_view_ = new OverlayView(delegate_);
1846   overlay_view_->SetVisible(false);
1847 }
1848
1849 views::View* AutofillDialogViews::CreateDetailsContainer() {
1850   details_container_ = new DetailsContainerView(
1851       base::Bind(&AutofillDialogViews::DetailsContainerBoundsChanged,
1852                  base::Unretained(this)));
1853
1854   // A box layout is used because it respects widget visibility.
1855   details_container_->SetLayoutManager(
1856       new views::BoxLayout(views::BoxLayout::kVertical, 0, 0, 0));
1857   for (DetailGroupMap::iterator iter = detail_groups_.begin();
1858        iter != detail_groups_.end(); ++iter) {
1859     CreateDetailsSection(iter->second.section);
1860     details_container_->AddChildView(iter->second.container);
1861   }
1862
1863   return details_container_;
1864 }
1865
1866 void AutofillDialogViews::CreateDetailsSection(DialogSection section) {
1867   // Inputs container (manual inputs + combobox).
1868   views::View* inputs_container = CreateInputsContainer(section);
1869
1870   DetailsGroup* group = GroupForSection(section);
1871   // Container (holds label + inputs).
1872   group->container = new SectionContainer(delegate_->LabelForSection(section),
1873                                           inputs_container,
1874                                           group->suggested_button);
1875   DCHECK(group->suggested_button->parent());
1876   UpdateDetailsGroupState(*group);
1877 }
1878
1879 views::View* AutofillDialogViews::CreateInputsContainer(DialogSection section) {
1880   // The |info_view| holds |manual_inputs| and |suggested_info|, allowing the
1881   // dialog to toggle which is shown.
1882   views::View* info_view = new views::View();
1883   info_view->SetLayoutManager(
1884       new views::BoxLayout(views::BoxLayout::kVertical, 0, 0, 0));
1885
1886   DetailsGroup* group = GroupForSection(section);
1887   group->manual_input = new views::View();
1888   InitInputsView(section);
1889   info_view->AddChildView(group->manual_input);
1890
1891   group->suggested_info = new SuggestionView(this);
1892   info_view->AddChildView(group->suggested_info);
1893
1894   // TODO(estade): It might be slightly more OO if this button were created
1895   // and listened to by the section container.
1896   group->suggested_button = new SuggestedButton(this);
1897
1898   return info_view;
1899 }
1900
1901 // TODO(estade): we should be using Chrome-style constrained window padding
1902 // values.
1903 void AutofillDialogViews::InitInputsView(DialogSection section) {
1904   DetailsGroup* group = GroupForSection(section);
1905   EraseInvalidViewsInGroup(group);
1906
1907   TextfieldMap* textfields = &group->textfields;
1908   textfields->clear();
1909
1910   ComboboxMap* comboboxes = &group->comboboxes;
1911   comboboxes->clear();
1912
1913   views::View* view = group->manual_input;
1914   view->RemoveAllChildViews(true);
1915
1916   views::GridLayout* layout = new views::GridLayout(view);
1917   view->SetLayoutManager(layout);
1918
1919   int column_set_id = 0;
1920   const DetailInputs& inputs = delegate_->RequestedFieldsForSection(section);
1921   for (DetailInputs::const_iterator it = inputs.begin();
1922        it != inputs.end(); ++it) {
1923     const DetailInput& input = *it;
1924
1925     ui::ComboboxModel* input_model =
1926         delegate_->ComboboxModelForAutofillType(input.type);
1927     scoped_ptr<views::View> view_to_add;
1928     if (input_model) {
1929       views::Combobox* combobox = new views::Combobox(input_model);
1930       combobox->set_listener(this);
1931       comboboxes->insert(std::make_pair(input.type, combobox));
1932       SelectComboboxValueOrSetToDefault(combobox, input.initial_value);
1933       view_to_add.reset(combobox);
1934     } else {
1935       DecoratedTextfield* field = new DecoratedTextfield(input.initial_value,
1936                                                          input.placeholder_text,
1937                                                          this);
1938       textfields->insert(std::make_pair(input.type, field));
1939       view_to_add.reset(field);
1940     }
1941
1942     if (input.length == DetailInput::NONE) {
1943       other_owned_views_.push_back(view_to_add.release());
1944       continue;
1945     }
1946
1947     if (input.length == DetailInput::LONG)
1948       ++column_set_id;
1949
1950     views::ColumnSet* column_set = layout->GetColumnSet(column_set_id);
1951     if (!column_set) {
1952       // Create a new column set and row.
1953       column_set = layout->AddColumnSet(column_set_id);
1954       if (it != inputs.begin())
1955         layout->AddPaddingRow(0, kManualInputRowPadding);
1956       layout->StartRow(0, column_set_id);
1957     } else {
1958       // Add a new column to existing row.
1959       column_set->AddPaddingColumn(0, views::kRelatedControlHorizontalSpacing);
1960       // Must explicitly skip the padding column since we've already started
1961       // adding views.
1962       layout->SkipColumns(1);
1963     }
1964
1965     float expand = input.expand_weight;
1966     column_set->AddColumn(views::GridLayout::FILL,
1967                           views::GridLayout::FILL,
1968                           expand ? expand : 1.0,
1969                           views::GridLayout::USE_PREF,
1970                           0,
1971                           0);
1972
1973     // This is the same as AddView(view_to_add), except that 1 is used for the
1974     // view's preferred width. Thus the width of the column completely depends
1975     // on |expand|.
1976     layout->AddView(view_to_add.release(), 1, 1,
1977                     views::GridLayout::FILL, views::GridLayout::FILL,
1978                     1, 0);
1979
1980     if (input.length == DetailInput::LONG ||
1981         input.length == DetailInput::SHORT_EOL) {
1982       ++column_set_id;
1983     }
1984   }
1985
1986   SetIconsForSection(section);
1987 }
1988
1989 void AutofillDialogViews::ShowDialogInMode(DialogMode dialog_mode) {
1990   loading_shield_->SetVisible(dialog_mode == LOADING);
1991   sign_in_web_view_->SetVisible(dialog_mode == SIGN_IN);
1992   notification_area_->SetVisible(dialog_mode == DETAIL_INPUT);
1993   scrollable_area_->SetVisible(dialog_mode == DETAIL_INPUT);
1994   FocusInitialView();
1995 }
1996
1997 void AutofillDialogViews::UpdateSectionImpl(
1998     DialogSection section,
1999     bool clobber_inputs) {
2000   DetailsGroup* group = GroupForSection(section);
2001
2002   if (clobber_inputs) {
2003     ServerFieldType type = UNKNOWN_TYPE;
2004     views::View* focused = GetFocusManager()->GetFocusedView();
2005     if (focused && group->container->Contains(focused)) {
2006       // Remember which view was focused before the inputs are clobbered.
2007       if (focused->GetClassName() == DecoratedTextfield::kViewClassName)
2008         type = TypeForTextfield(static_cast<DecoratedTextfield*>(focused));
2009       else if (focused->GetClassName() == views::Combobox::kViewClassName)
2010         type = TypeForCombobox(static_cast<views::Combobox*>(focused));
2011     }
2012
2013     InitInputsView(section);
2014
2015     if (type != UNKNOWN_TYPE) {
2016       // Restore the focus to the input with the previous type (e.g. country).
2017       views::View* to_focus = TextfieldForType(type);
2018       if (!to_focus) to_focus = ComboboxForType(type);
2019       if (to_focus)
2020         to_focus->RequestFocus();
2021     }
2022   } else {
2023     const DetailInputs& updated_inputs =
2024         delegate_->RequestedFieldsForSection(section);
2025
2026     for (DetailInputs::const_iterator iter = updated_inputs.begin();
2027          iter != updated_inputs.end(); ++iter) {
2028       const DetailInput& input = *iter;
2029
2030       TextfieldMap::iterator text_mapping = group->textfields.find(input.type);
2031       if (text_mapping != group->textfields.end()) {
2032         DecoratedTextfield* decorated = text_mapping->second;
2033         if (decorated->text().empty())
2034           decorated->SetText(input.initial_value);
2035       }
2036
2037       ComboboxMap::iterator combo_mapping = group->comboboxes.find(input.type);
2038       if (combo_mapping != group->comboboxes.end()) {
2039         views::Combobox* combobox = combo_mapping->second;
2040         if (combobox->selected_index() == combobox->model()->GetDefaultIndex())
2041           SelectComboboxValueOrSetToDefault(combobox, input.initial_value);
2042       }
2043     }
2044
2045     SetIconsForSection(section);
2046   }
2047
2048   SetEditabilityForSection(section);
2049   UpdateDetailsGroupState(*group);
2050 }
2051
2052 void AutofillDialogViews::UpdateDetailsGroupState(const DetailsGroup& group) {
2053   const SuggestionState& suggestion_state =
2054       delegate_->SuggestionStateForSection(group.section);
2055   group.suggested_info->SetState(suggestion_state);
2056   group.manual_input->SetVisible(!suggestion_state.visible);
2057
2058   UpdateButtonStripExtraView();
2059
2060   const bool has_menu = !!delegate_->MenuModelForSection(group.section);
2061
2062   if (group.suggested_button)
2063     group.suggested_button->SetVisible(has_menu);
2064
2065   if (group.container) {
2066     group.container->SetForwardMouseEvents(
2067         has_menu && suggestion_state.visible);
2068     group.container->SetVisible(delegate_->SectionIsActive(group.section));
2069     if (group.container->visible())
2070       ValidateGroup(group, VALIDATE_EDIT);
2071   }
2072
2073   ContentsPreferredSizeChanged();
2074 }
2075
2076 void AutofillDialogViews::FocusInitialView() {
2077   views::View* to_focus = GetInitiallyFocusedView();
2078   if (to_focus && !to_focus->HasFocus())
2079     to_focus->RequestFocus();
2080 }
2081
2082 template<class T>
2083 void AutofillDialogViews::SetValidityForInput(
2084     T* input,
2085     const base::string16& message) {
2086   bool invalid = !message.empty();
2087   input->SetInvalid(invalid);
2088
2089   if (invalid) {
2090     validity_map_[input] = message;
2091   } else {
2092     validity_map_.erase(input);
2093
2094     if (error_bubble_ && error_bubble_->anchor() == input) {
2095       validity_map_.erase(input);
2096       HideErrorBubble();
2097     }
2098   }
2099 }
2100
2101 void AutofillDialogViews::ShowErrorBubbleForViewIfNecessary(views::View* view) {
2102   if (!view->GetWidget())
2103     return;
2104
2105   if (!delegate_->ShouldShowErrorBubble()) {
2106     DCHECK(!error_bubble_);
2107     return;
2108   }
2109
2110   std::map<views::View*, base::string16>::iterator error_message =
2111       validity_map_.find(view);
2112   if (error_message != validity_map_.end()) {
2113     view->ScrollRectToVisible(view->GetLocalBounds());
2114
2115     if (!error_bubble_ || error_bubble_->anchor() != view) {
2116       HideErrorBubble();
2117       error_bubble_ = new InfoBubble(view, error_message->second);
2118       error_bubble_->set_align_to_anchor_edge(true);
2119       error_bubble_->set_preferred_width(
2120           (kSectionContainerWidth - views::kRelatedControlVerticalSpacing) / 2);
2121       bool show_above = view->GetClassName() == views::Combobox::kViewClassName;
2122       error_bubble_->set_show_above_anchor(show_above);
2123       error_bubble_->Show();
2124       observer_.Add(error_bubble_->GetWidget());
2125     }
2126   }
2127 }
2128
2129 void AutofillDialogViews::HideErrorBubble() {
2130   if (error_bubble_)
2131     error_bubble_->Hide();
2132 }
2133
2134 void AutofillDialogViews::MarkInputsInvalid(
2135     DialogSection section,
2136     const ValidityMessages& messages,
2137     bool overwrite_unsure) {
2138   DetailsGroup* group = GroupForSection(section);
2139   DCHECK(group->container->visible());
2140
2141   if (group->manual_input->visible()) {
2142     for (TextfieldMap::const_iterator iter = group->textfields.begin();
2143          iter != group->textfields.end(); ++iter) {
2144       const ValidityMessage& message =
2145           messages.GetMessageOrDefault(iter->first);
2146       if (overwrite_unsure || message.sure)
2147         SetValidityForInput(iter->second, message.text);
2148     }
2149     for (ComboboxMap::const_iterator iter = group->comboboxes.begin();
2150          iter != group->comboboxes.end(); ++iter) {
2151       const ValidityMessage& message =
2152           messages.GetMessageOrDefault(iter->first);
2153       if (overwrite_unsure || message.sure)
2154         SetValidityForInput(iter->second, message.text);
2155     }
2156   } else {
2157     EraseInvalidViewsInGroup(group);
2158
2159     if (section == GetCreditCardSection()) {
2160       // Special case CVC as it's not part of |group->manual_input|.
2161       const ValidityMessage& message =
2162           messages.GetMessageOrDefault(CREDIT_CARD_VERIFICATION_CODE);
2163       if (overwrite_unsure || message.sure) {
2164         SetValidityForInput(group->suggested_info->decorated_textfield(),
2165                             message.text);
2166       }
2167     }
2168   }
2169 }
2170
2171 bool AutofillDialogViews::ValidateGroup(const DetailsGroup& group,
2172                                         ValidationType validation_type) {
2173   DCHECK(group.container->visible());
2174
2175   FieldValueMap detail_outputs;
2176
2177   if (group.manual_input->visible()) {
2178     for (TextfieldMap::const_iterator iter = group.textfields.begin();
2179          iter != group.textfields.end(); ++iter) {
2180       if (!iter->second->editable())
2181         continue;
2182
2183       detail_outputs[iter->first] = iter->second->text();
2184     }
2185     for (ComboboxMap::const_iterator iter = group.comboboxes.begin();
2186          iter != group.comboboxes.end(); ++iter) {
2187       if (!iter->second->enabled())
2188         continue;
2189
2190       views::Combobox* combobox = iter->second;
2191       base::string16 item =
2192           combobox->model()->GetItemAt(combobox->selected_index());
2193       detail_outputs[iter->first] = item;
2194     }
2195   } else if (group.section == GetCreditCardSection()) {
2196     DecoratedTextfield* decorated_cvc =
2197         group.suggested_info->decorated_textfield();
2198     if (decorated_cvc->visible())
2199       detail_outputs[CREDIT_CARD_VERIFICATION_CODE] = decorated_cvc->text();
2200   }
2201
2202   ValidityMessages validity = delegate_->InputsAreValid(group.section,
2203                                                         detail_outputs);
2204   MarkInputsInvalid(group.section, validity, validation_type == VALIDATE_FINAL);
2205
2206   // If there are any validation errors, sure or unsure, the group is invalid.
2207   return !validity.HasErrors();
2208 }
2209
2210 bool AutofillDialogViews::ValidateForm() {
2211   bool all_valid = true;
2212   validity_map_.clear();
2213
2214   for (DetailGroupMap::iterator iter = detail_groups_.begin();
2215        iter != detail_groups_.end(); ++iter) {
2216     const DetailsGroup& group = iter->second;
2217     if (!group.container->visible())
2218       continue;
2219
2220     if (!ValidateGroup(group, VALIDATE_FINAL))
2221       all_valid = false;
2222   }
2223
2224   return all_valid;
2225 }
2226
2227 void AutofillDialogViews::InputEditedOrActivated(ServerFieldType type,
2228                                                  const gfx::Rect& bounds,
2229                                                  bool was_edit) {
2230   DCHECK_NE(UNKNOWN_TYPE, type);
2231
2232   DecoratedTextfield* decorated = TextfieldForType(type);
2233   views::Combobox* combobox = ComboboxForType(type);
2234
2235   // Both views may be NULL if the event comes from an inactive section, which
2236   // may occur when using an IME.
2237   if (!combobox && !decorated)
2238     return;
2239
2240   DCHECK_NE(!!combobox, !!decorated);
2241   DetailsGroup* group = decorated ? GroupForView(decorated) :
2242                                     GroupForView(combobox);
2243   base::string16 text = decorated ?
2244       decorated->text() :
2245       combobox->model()->GetItemAt(combobox->selected_index());
2246   DCHECK(group);
2247
2248   delegate_->UserEditedOrActivatedInput(group->section,
2249                                         type,
2250                                         GetWidget()->GetNativeView(),
2251                                         bounds,
2252                                         text,
2253                                         was_edit);
2254
2255   // If the field is a textfield and is invalid, check if the text is now valid.
2256   // Many fields (i.e. CC#) are invalid for most of the duration of editing,
2257   // so flagging them as invalid prematurely is not helpful. However,
2258   // correcting a minor mistake (i.e. a wrong CC digit) should immediately
2259   // result in validation - positive user feedback.
2260   if (decorated && decorated->invalid() && was_edit) {
2261     SetValidityForInput(
2262         decorated,
2263         delegate_->InputValidityMessage(
2264             group->section, type, decorated->text()));
2265
2266     // If the field transitioned from invalid to valid, re-validate the group,
2267     // since inter-field checks become meaningful with valid fields.
2268     if (!decorated->invalid())
2269       ValidateGroup(*group, VALIDATE_EDIT);
2270   }
2271
2272   if (delegate_->FieldControlsIcons(type))
2273     SetIconsForSection(group->section);
2274
2275   SetEditabilityForSection(group->section);
2276 }
2277
2278 void AutofillDialogViews::UpdateButtonStripExtraView() {
2279   save_in_chrome_checkbox_container_->SetVisible(
2280       delegate_->ShouldOfferToSaveInChrome());
2281
2282   gfx::Image image = delegate_->ButtonStripImage();
2283   button_strip_image_->SetVisible(!image.IsEmpty());
2284   button_strip_image_->SetImage(image.AsImageSkia());
2285 }
2286
2287 void AutofillDialogViews::ContentsPreferredSizeChanged() {
2288   if (updates_scope_ != 0) {
2289     needs_update_ = true;
2290     return;
2291   }
2292
2293   preferred_size_ = gfx::Size();
2294
2295   if (GetWidget() && delegate_ && delegate_->GetWebContents()) {
2296     UpdateWebContentsModalDialogPosition(
2297         GetWidget(),
2298         WebContentsModalDialogManager::FromWebContents(
2299             delegate_->GetWebContents())->delegate()->
2300                 GetWebContentsModalDialogHost());
2301     SetBoundsRect(bounds());
2302   }
2303 }
2304
2305 AutofillDialogViews::DetailsGroup* AutofillDialogViews::GroupForSection(
2306     DialogSection section) {
2307   return &detail_groups_.find(section)->second;
2308 }
2309
2310 AutofillDialogViews::DetailsGroup* AutofillDialogViews::GroupForView(
2311     views::View* view) {
2312   DCHECK(view);
2313
2314   for (DetailGroupMap::iterator iter = detail_groups_.begin();
2315        iter != detail_groups_.end(); ++iter) {
2316     DetailsGroup* group = &iter->second;
2317     if (view->parent() == group->manual_input)
2318       return group;
2319
2320     views::View* decorated =
2321         view->GetAncestorWithClassName(DecoratedTextfield::kViewClassName);
2322
2323     // Textfields need to check a second case, since they can be suggested
2324     // inputs instead of directly editable inputs. Those are accessed via
2325     // |suggested_info|.
2326     if (decorated &&
2327         decorated == group->suggested_info->decorated_textfield()) {
2328       return group;
2329     }
2330   }
2331
2332   return NULL;
2333 }
2334
2335 void AutofillDialogViews::EraseInvalidViewsInGroup(const DetailsGroup* group) {
2336   std::map<views::View*, base::string16>::iterator it = validity_map_.begin();
2337   while (it != validity_map_.end()) {
2338     if (GroupForView(it->first) == group)
2339       validity_map_.erase(it++);
2340     else
2341       ++it;
2342   }
2343 }
2344
2345 DecoratedTextfield* AutofillDialogViews::TextfieldForType(
2346     ServerFieldType type) {
2347   if (type == CREDIT_CARD_VERIFICATION_CODE) {
2348     DetailsGroup* group = GroupForSection(GetCreditCardSection());
2349     if (!group->manual_input->visible())
2350       return group->suggested_info->decorated_textfield();
2351   }
2352
2353   for (DetailGroupMap::iterator iter = detail_groups_.begin();
2354        iter != detail_groups_.end(); ++iter) {
2355     const DetailsGroup& group = iter->second;
2356     if (!delegate_->SectionIsActive(group.section))
2357       continue;
2358
2359     TextfieldMap::const_iterator text_mapping = group.textfields.find(type);
2360     if (text_mapping != group.textfields.end())
2361       return text_mapping->second;
2362   }
2363
2364   return NULL;
2365 }
2366
2367 ServerFieldType AutofillDialogViews::TypeForTextfield(
2368     const views::Textfield* textfield) {
2369   DetailsGroup* cc_group = GroupForSection(GetCreditCardSection());
2370   if (textfield == cc_group->suggested_info->decorated_textfield())
2371     return CREDIT_CARD_VERIFICATION_CODE;
2372
2373   for (DetailGroupMap::const_iterator it = detail_groups_.begin();
2374        it != detail_groups_.end(); ++it) {
2375     if (!delegate_->SectionIsActive(it->second.section))
2376       continue;
2377
2378     for (TextfieldMap::const_iterator text_it = it->second.textfields.begin();
2379          text_it != it->second.textfields.end(); ++text_it) {
2380       if (textfield == text_it->second)
2381         return text_it->first;
2382     }
2383   }
2384
2385   return UNKNOWN_TYPE;
2386 }
2387
2388 views::Combobox* AutofillDialogViews::ComboboxForType(
2389     ServerFieldType type) {
2390   for (DetailGroupMap::iterator iter = detail_groups_.begin();
2391        iter != detail_groups_.end(); ++iter) {
2392     const DetailsGroup& group = iter->second;
2393     if (!delegate_->SectionIsActive(group.section))
2394       continue;
2395
2396     ComboboxMap::const_iterator combo_mapping = group.comboboxes.find(type);
2397     if (combo_mapping != group.comboboxes.end())
2398       return combo_mapping->second;
2399   }
2400
2401   return NULL;
2402 }
2403
2404 ServerFieldType AutofillDialogViews::TypeForCombobox(
2405     const views::Combobox* combobox) const {
2406   for (DetailGroupMap::const_iterator it = detail_groups_.begin();
2407        it != detail_groups_.end(); ++it) {
2408     const DetailsGroup& group = it->second;
2409     if (!delegate_->SectionIsActive(group.section))
2410       continue;
2411
2412     for (ComboboxMap::const_iterator combo_it = group.comboboxes.begin();
2413          combo_it != group.comboboxes.end(); ++combo_it) {
2414       if (combo_it->second == combobox)
2415         return combo_it->first;
2416     }
2417   }
2418
2419   return UNKNOWN_TYPE;
2420 }
2421
2422 void AutofillDialogViews::DetailsContainerBoundsChanged() {
2423   if (error_bubble_)
2424     error_bubble_->UpdatePosition();
2425 }
2426
2427 void AutofillDialogViews::SetIconsForSection(DialogSection section) {
2428   FieldValueMap user_input;
2429   GetUserInput(section, &user_input);
2430   FieldIconMap field_icons = delegate_->IconsForFields(user_input);
2431   TextfieldMap* textfields = &GroupForSection(section)->textfields;
2432   for (TextfieldMap::const_iterator textfield_it = textfields->begin();
2433        textfield_it != textfields->end();
2434        ++textfield_it) {
2435     ServerFieldType field_type = textfield_it->first;
2436     FieldIconMap::const_iterator field_icon_it = field_icons.find(field_type);
2437     DecoratedTextfield* textfield = textfield_it->second;
2438     if (field_icon_it != field_icons.end())
2439       textfield->SetIcon(field_icon_it->second);
2440     else
2441       textfield->SetTooltipIcon(delegate_->TooltipForField(field_type));
2442   }
2443 }
2444
2445 void AutofillDialogViews::SetEditabilityForSection(DialogSection section) {
2446   const DetailInputs& inputs =
2447       delegate_->RequestedFieldsForSection(section);
2448   DetailsGroup* group = GroupForSection(section);
2449
2450   for (DetailInputs::const_iterator iter = inputs.begin();
2451        iter != inputs.end(); ++iter) {
2452     const DetailInput& input = *iter;
2453     bool editable = delegate_->InputIsEditable(input, section);
2454
2455     TextfieldMap::iterator text_mapping = group->textfields.find(input.type);
2456     if (text_mapping != group->textfields.end()) {
2457       DecoratedTextfield* decorated = text_mapping->second;
2458       decorated->SetEditable(editable);
2459       continue;
2460     }
2461
2462     ComboboxMap::iterator combo_mapping = group->comboboxes.find(input.type);
2463     if (combo_mapping != group->comboboxes.end()) {
2464       views::Combobox* combobox = combo_mapping->second;
2465       combobox->SetEnabled(editable);
2466     }
2467   }
2468 }
2469
2470 AutofillDialogViews::DetailsGroup::DetailsGroup(DialogSection section)
2471     : section(section),
2472       container(NULL),
2473       manual_input(NULL),
2474       suggested_info(NULL),
2475       suggested_button(NULL) {}
2476
2477 AutofillDialogViews::DetailsGroup::~DetailsGroup() {}
2478
2479 }  // namespace autofill