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