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