autofill_dialog_views.cc revision bb1529ce867d8845a77ec7cdf3e3003ef1771a40
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      save_in_chrome_checkbox_container_(NULL),
1119      button_strip_image_(NULL),
1120      autocheckout_steps_area_(NULL),
1121      autocheckout_progress_bar_view_(NULL),
1122      autocheckout_progress_bar_(NULL),
1123      footnote_view_(NULL),
1124      legal_document_view_(NULL),
1125      focus_manager_(NULL),
1126      observer_(this) {
1127  DCHECK(delegate);
1128  detail_groups_.insert(std::make_pair(SECTION_EMAIL,
1129                                       DetailsGroup(SECTION_EMAIL)));
1130  detail_groups_.insert(std::make_pair(SECTION_CC,
1131                                       DetailsGroup(SECTION_CC)));
1132  detail_groups_.insert(std::make_pair(SECTION_BILLING,
1133                                       DetailsGroup(SECTION_BILLING)));
1134  detail_groups_.insert(std::make_pair(SECTION_CC_BILLING,
1135                                       DetailsGroup(SECTION_CC_BILLING)));
1136  detail_groups_.insert(std::make_pair(SECTION_SHIPPING,
1137                                       DetailsGroup(SECTION_SHIPPING)));
1138}
1139
1140AutofillDialogViews::~AutofillDialogViews() {
1141  DCHECK(!window_);
1142}
1143
1144void AutofillDialogViews::Show() {
1145  InitChildViews();
1146  UpdateAccountChooser();
1147  UpdateNotificationArea();
1148  UpdateButtonStripExtraView();
1149
1150  // Ownership of |contents_| is handed off by this call. The widget will take
1151  // care of deleting itself after calling DeleteDelegate().
1152  WebContentsModalDialogManager* web_contents_modal_dialog_manager =
1153      WebContentsModalDialogManager::FromWebContents(
1154          delegate_->GetWebContents());
1155  window_ = CreateWebContentsModalDialogViews(
1156      this,
1157      delegate_->GetWebContents()->GetView()->GetNativeView(),
1158      web_contents_modal_dialog_manager->delegate()->
1159          GetWebContentsModalDialogHost());
1160  web_contents_modal_dialog_manager->ShowDialog(window_->GetNativeView());
1161  focus_manager_ = window_->GetFocusManager();
1162  focus_manager_->AddFocusChangeListener(this);
1163
1164  // Listen for size changes on the browser.
1165  views::Widget* browser_widget =
1166      views::Widget::GetTopLevelWidgetForNativeView(
1167          delegate_->GetWebContents()->GetView()->GetNativeView());
1168  observer_.Add(browser_widget);
1169
1170  gfx::Image splash_image = delegate_->SplashPageImage();
1171  if (!splash_image.IsEmpty()) {
1172    DialogOverlayState state;
1173    state.image = splash_image;
1174    overlay_view_->SetState(state, NULL);
1175    overlay_view_->BeginFadeOut();
1176  }
1177}
1178
1179void AutofillDialogViews::Hide() {
1180  if (window_)
1181    window_->Close();
1182}
1183
1184void AutofillDialogViews::UpdateAccountChooser() {
1185  account_chooser_->Update();
1186  // TODO(estade): replace this with a better loading image/animation.
1187  // See http://crbug.com/230932
1188  base::string16 new_loading_message = (delegate_->ShouldShowSpinner() ?
1189      ASCIIToUTF16("Loading...") : base::string16());
1190  if (new_loading_message != loading_shield_->text()) {
1191    loading_shield_->SetText(new_loading_message);
1192    loading_shield_->SetVisible(!new_loading_message.empty());
1193    Layout();
1194  }
1195
1196  // Update legal documents for the account.
1197  if (footnote_view_) {
1198    const base::string16 text = delegate_->LegalDocumentsText();
1199    legal_document_view_->SetText(text);
1200
1201    if (!text.empty()) {
1202      const std::vector<ui::Range>& link_ranges =
1203          delegate_->LegalDocumentLinks();
1204      for (size_t i = 0; i < link_ranges.size(); ++i) {
1205        legal_document_view_->AddStyleRange(
1206            link_ranges[i],
1207            views::StyledLabel::RangeStyleInfo::CreateForLink());
1208      }
1209    }
1210
1211    footnote_view_->SetVisible(!text.empty());
1212    ContentsPreferredSizeChanged();
1213  }
1214}
1215
1216void AutofillDialogViews::UpdateAutocheckoutStepsArea() {
1217  autocheckout_steps_area_->SetSteps(delegate_->CurrentAutocheckoutSteps());
1218  ContentsPreferredSizeChanged();
1219}
1220
1221void AutofillDialogViews::UpdateButtonStrip() {
1222  button_strip_extra_view_->SetVisible(
1223      GetDialogButtons() != ui::DIALOG_BUTTON_NONE);
1224  UpdateButtonStripExtraView();
1225  GetDialogClientView()->UpdateDialogButtons();
1226
1227  overlay_view_->SetState(delegate_->GetDialogOverlay(), this);
1228
1229  ContentsPreferredSizeChanged();
1230}
1231
1232void AutofillDialogViews::UpdateDetailArea() {
1233  scrollable_area_->SetVisible(delegate_->ShouldShowDetailArea());
1234  ContentsPreferredSizeChanged();
1235}
1236
1237void AutofillDialogViews::UpdateForErrors() {
1238  ValidateForm();
1239}
1240
1241void AutofillDialogViews::UpdateNotificationArea() {
1242  DCHECK(notification_area_);
1243  notification_area_->SetNotifications(delegate_->CurrentNotifications());
1244  ContentsPreferredSizeChanged();
1245}
1246
1247void AutofillDialogViews::UpdateSection(DialogSection section) {
1248  UpdateSectionImpl(section, true);
1249}
1250
1251void AutofillDialogViews::FillSection(DialogSection section,
1252                                      const DetailInput& originating_input) {
1253  DetailsGroup* group = GroupForSection(section);
1254  // Make sure to overwrite the originating input.
1255  TextfieldMap::iterator text_mapping =
1256      group->textfields.find(&originating_input);
1257  if (text_mapping != group->textfields.end())
1258    text_mapping->second->SetText(base::string16());
1259
1260  // If the Autofill data comes from a credit card, make sure to overwrite the
1261  // CC comboboxes (even if they already have something in them). If the
1262  // Autofill data comes from an AutofillProfile, leave the comboboxes alone.
1263  if ((section == SECTION_CC || section == SECTION_CC_BILLING) &&
1264      AutofillType(originating_input.type).group() == CREDIT_CARD) {
1265    for (ComboboxMap::const_iterator it = group->comboboxes.begin();
1266         it != group->comboboxes.end(); ++it) {
1267      if (AutofillType(it->first->type).group() == CREDIT_CARD)
1268        it->second->SetSelectedIndex(it->second->model()->GetDefaultIndex());
1269    }
1270  }
1271
1272  UpdateSectionImpl(section, false);
1273}
1274
1275void AutofillDialogViews::GetUserInput(DialogSection section,
1276                                       DetailOutputMap* output) {
1277  DetailsGroup* group = GroupForSection(section);
1278  for (TextfieldMap::const_iterator it = group->textfields.begin();
1279       it != group->textfields.end(); ++it) {
1280    output->insert(std::make_pair(it->first, it->second->text()));
1281  }
1282  for (ComboboxMap::const_iterator it = group->comboboxes.begin();
1283       it != group->comboboxes.end(); ++it) {
1284    output->insert(std::make_pair(it->first,
1285        it->second->model()->GetItemAt(it->second->selected_index())));
1286  }
1287}
1288
1289base::string16 AutofillDialogViews::GetCvc() {
1290  DialogSection billing_section = delegate_->SectionIsActive(SECTION_CC) ?
1291      SECTION_CC : SECTION_CC_BILLING;
1292  return GroupForSection(billing_section)->suggested_info->
1293      decorated_textfield()->text();
1294}
1295
1296bool AutofillDialogViews::SaveDetailsLocally() {
1297  DCHECK(save_in_chrome_checkbox_->visible());
1298  return save_in_chrome_checkbox_->checked();
1299}
1300
1301const content::NavigationController* AutofillDialogViews::ShowSignIn() {
1302  // TODO(abodenha) We should be able to use the WebContents of the WebView
1303  // to navigate instead of LoadInitialURL.  Figure out why it doesn't work.
1304
1305  sign_in_webview_->LoadInitialURL(wallet::GetSignInUrl());
1306
1307  sign_in_webview_->SetVisible(true);
1308  sign_in_webview_->RequestFocus();
1309  UpdateButtonStrip();
1310  ContentsPreferredSizeChanged();
1311  return &sign_in_webview_->web_contents()->GetController();
1312}
1313
1314void AutofillDialogViews::HideSignIn() {
1315  sign_in_webview_->SetVisible(false);
1316  UpdateButtonStrip();
1317  ContentsPreferredSizeChanged();
1318}
1319
1320void AutofillDialogViews::UpdateProgressBar(double value) {
1321  autocheckout_progress_bar_->SetValue(value);
1322}
1323
1324void AutofillDialogViews::ModelChanged() {
1325  menu_runner_.reset();
1326
1327  for (DetailGroupMap::const_iterator iter = detail_groups_.begin();
1328       iter != detail_groups_.end(); ++iter) {
1329    UpdateDetailsGroupState(iter->second);
1330  }
1331}
1332
1333TestableAutofillDialogView* AutofillDialogViews::GetTestableView() {
1334  return this;
1335}
1336
1337void AutofillDialogViews::OnSignInResize(const gfx::Size& pref_size) {
1338  sign_in_webview_->SetPreferredSize(pref_size);
1339  ContentsPreferredSizeChanged();
1340}
1341
1342void AutofillDialogViews::SubmitForTesting() {
1343  Accept();
1344}
1345
1346void AutofillDialogViews::CancelForTesting() {
1347  GetDialogClientView()->CancelWindow();
1348}
1349
1350base::string16 AutofillDialogViews::GetTextContentsOfInput(
1351    const DetailInput& input) {
1352  views::Textfield* textfield = TextfieldForInput(input);
1353  if (textfield)
1354    return textfield->text();
1355
1356  views::Combobox* combobox = ComboboxForInput(input);
1357  if (combobox)
1358    return combobox->model()->GetItemAt(combobox->selected_index());
1359
1360  NOTREACHED();
1361  return base::string16();
1362}
1363
1364void AutofillDialogViews::SetTextContentsOfInput(
1365    const DetailInput& input,
1366    const base::string16& contents) {
1367  views::Textfield* textfield = TextfieldForInput(input);
1368  if (textfield) {
1369    TextfieldForInput(input)->SetText(contents);
1370    return;
1371  }
1372
1373  views::Combobox* combobox = ComboboxForInput(input);
1374  if (combobox) {
1375    for (int i = 0; i < combobox->model()->GetItemCount(); ++i) {
1376      if (contents == combobox->model()->GetItemAt(i)) {
1377        combobox->SetSelectedIndex(i);
1378        return;
1379      }
1380    }
1381    // If we don't find a match, return the combobox to its default state.
1382    combobox->SetSelectedIndex(combobox->model()->GetDefaultIndex());
1383    return;
1384  }
1385
1386  NOTREACHED();
1387}
1388
1389void AutofillDialogViews::SetTextContentsOfSuggestionInput(
1390    DialogSection section,
1391    const base::string16& text) {
1392  GroupForSection(section)->suggested_info->decorated_textfield()->
1393      SetText(text);
1394}
1395
1396void AutofillDialogViews::ActivateInput(const DetailInput& input) {
1397  TextfieldEditedOrActivated(TextfieldForInput(input), false);
1398}
1399
1400gfx::Size AutofillDialogViews::GetSize() const {
1401  return GetWidget() ? GetWidget()->GetRootView()->size() : gfx::Size();
1402}
1403
1404gfx::Size AutofillDialogViews::GetPreferredSize() {
1405  gfx::Insets insets = GetInsets();
1406  gfx::Size scroll_size = scrollable_area_->contents()->GetPreferredSize();
1407  int width = scroll_size.width() + insets.width();
1408
1409  if (sign_in_webview_->visible()) {
1410    gfx::Size size = static_cast<views::View*>(sign_in_webview_)->
1411        GetPreferredSize();
1412    return gfx::Size(width, size.height() + insets.height());
1413  }
1414
1415  int base_height = insets.height();
1416  int notification_height = notification_area_->
1417      GetHeightForWidth(scroll_size.width());
1418  if (notification_height > 0)
1419    base_height += notification_height + views::kRelatedControlVerticalSpacing;
1420
1421  int steps_height = autocheckout_steps_area_->
1422      GetHeightForWidth(scroll_size.width());
1423  if (steps_height > 0)
1424    base_height += steps_height + views::kRelatedControlVerticalSpacing;
1425
1426  gfx::Size preferred_size;
1427  // When the scroll area isn't visible, it still sets the width but doesn't
1428  // factor into height.
1429  if (!scrollable_area_->visible()) {
1430    preferred_size.SetSize(width, base_height);
1431  } else {
1432    // Show as much of the scroll view as is possible without going past the
1433    // bottom of the browser window.
1434    views::Widget* widget =
1435        views::Widget::GetTopLevelWidgetForNativeView(
1436            delegate_->GetWebContents()->GetView()->GetNativeView());
1437    int browser_window_height =
1438        widget ? widget->GetContentsView()->bounds().height() : INT_MAX;
1439    const int kWindowDecorationHeight = 200;
1440    int height = base_height + std::min(
1441        scroll_size.height(),
1442        std::max(kMinimumContentsHeight,
1443                 browser_window_height - base_height -
1444                     kWindowDecorationHeight));
1445    preferred_size.SetSize(width, height);
1446  }
1447
1448  if (!overlay_view_->visible())
1449    return preferred_size;
1450
1451  int height_of_overlay =
1452      overlay_view_->GetHeightForContentsForWidth(preferred_size.width());
1453  if (height_of_overlay > 0)
1454    preferred_size.set_height(height_of_overlay);
1455
1456  return preferred_size;
1457}
1458
1459void AutofillDialogViews::Layout() {
1460  gfx::Rect content_bounds = GetContentsBounds();
1461  if (sign_in_webview_->visible()) {
1462    sign_in_webview_->SetBoundsRect(content_bounds);
1463    return;
1464  }
1465
1466  const int x = content_bounds.x();
1467  const int y = content_bounds.y();
1468  const int w = content_bounds.width();
1469  // Layout notification area at top of dialog.
1470  int notification_height = notification_area_->GetHeightForWidth(w);
1471  notification_area_->SetBounds(x, y, w, notification_height);
1472
1473  // Layout Autocheckout steps at bottom of dialog.
1474  int steps_height = autocheckout_steps_area_->GetHeightForWidth(w);
1475  autocheckout_steps_area_->SetBounds(x, content_bounds.bottom() - steps_height,
1476                                      w, steps_height);
1477
1478  // The rest (the |scrollable_area_|) takes up whatever's left.
1479  if (scrollable_area_->visible()) {
1480    int scroll_y = y;
1481    if (notification_height > 0)
1482      scroll_y += notification_height + views::kRelatedControlVerticalSpacing;
1483
1484    int scroll_bottom = content_bounds.bottom();
1485    if (steps_height > 0)
1486      scroll_bottom -= steps_height + views::kRelatedControlVerticalSpacing;
1487
1488    scrollable_area_->contents()->SizeToPreferredSize();
1489    scrollable_area_->SetBounds(x, scroll_y, w, scroll_bottom - scroll_y);
1490  }
1491
1492  if (loading_shield_->visible())
1493    loading_shield_->SetBoundsRect(bounds());
1494
1495  if (error_bubble_)
1496    error_bubble_->UpdatePosition();
1497}
1498
1499void AutofillDialogViews::OnBoundsChanged(const gfx::Rect& previous_bounds) {
1500  sign_in_delegate_->SetMinWidth(GetContentsBounds().width());
1501}
1502
1503base::string16 AutofillDialogViews::GetWindowTitle() const {
1504  return delegate_->DialogTitle();
1505}
1506
1507void AutofillDialogViews::WindowClosing() {
1508  focus_manager_->RemoveFocusChangeListener(this);
1509}
1510
1511void AutofillDialogViews::DeleteDelegate() {
1512  window_ = NULL;
1513  // |this| belongs to the controller (|delegate_|).
1514  delegate_->ViewClosed();
1515}
1516
1517int AutofillDialogViews::GetDialogButtons() const {
1518  if (sign_in_webview_->visible())
1519    return ui::DIALOG_BUTTON_NONE;
1520
1521  return delegate_->GetDialogButtons();
1522}
1523
1524base::string16 AutofillDialogViews::GetDialogButtonLabel(
1525    ui::DialogButton button) const {
1526  return button == ui::DIALOG_BUTTON_OK ?
1527      delegate_->ConfirmButtonText() : delegate_->CancelButtonText();
1528}
1529
1530bool AutofillDialogViews::ShouldDefaultButtonBeBlue() const {
1531  return true;
1532}
1533
1534bool AutofillDialogViews::IsDialogButtonEnabled(ui::DialogButton button) const {
1535  return delegate_->IsDialogButtonEnabled(button);
1536}
1537
1538views::View* AutofillDialogViews::CreateExtraView() {
1539  return button_strip_extra_view_;
1540}
1541
1542views::View* AutofillDialogViews::CreateTitlebarExtraView() {
1543  return account_chooser_;
1544}
1545
1546views::View* AutofillDialogViews::CreateFootnoteView() {
1547  footnote_view_ = new LayoutPropagationView();
1548  footnote_view_->SetLayoutManager(
1549      new views::BoxLayout(views::BoxLayout::kVertical,
1550                           kDialogEdgePadding,
1551                           kDialogEdgePadding,
1552                           0));
1553  footnote_view_->set_border(
1554      views::Border::CreateSolidSidedBorder(1, 0, 0, 0, kSubtleBorderColor));
1555  footnote_view_->set_background(
1556      views::Background::CreateSolidBackground(kShadingColor));
1557
1558  legal_document_view_ = new views::StyledLabel(base::string16(), this);
1559  views::StyledLabel::RangeStyleInfo default_style;
1560  default_style.color = kGreyTextColor;
1561  legal_document_view_->SetDefaultStyle(default_style);
1562
1563  footnote_view_->AddChildView(legal_document_view_);
1564  footnote_view_->SetVisible(false);
1565
1566  return footnote_view_;
1567}
1568
1569views::View* AutofillDialogViews::CreateOverlayView() {
1570  return overlay_view_;
1571}
1572
1573bool AutofillDialogViews::Cancel() {
1574  return delegate_->OnCancel();
1575}
1576
1577bool AutofillDialogViews::Accept() {
1578  if (ValidateForm())
1579    return delegate_->OnAccept();
1580
1581  if (!validity_map_.empty())
1582    validity_map_.begin()->first->RequestFocus();
1583  return false;
1584}
1585
1586// TODO(wittman): Remove this override once we move to the new style frame view
1587// on all dialogs.
1588views::NonClientFrameView* AutofillDialogViews::CreateNonClientFrameView(
1589    views::Widget* widget) {
1590  return CreateConstrainedStyleNonClientFrameView(
1591      widget,
1592      delegate_->GetWebContents()->GetBrowserContext());
1593}
1594
1595void AutofillDialogViews::ButtonPressed(views::Button* sender,
1596                                        const ui::Event& event) {
1597  if (sender->GetAncestorWithClassName(kOverlayViewClassName)) {
1598    delegate_->OverlayButtonPressed();
1599    return;
1600  }
1601
1602  // TODO(estade): Should the menu be shown on mouse down?
1603  DetailsGroup* group = NULL;
1604  for (DetailGroupMap::iterator iter = detail_groups_.begin();
1605       iter != detail_groups_.end(); ++iter) {
1606    if (sender == iter->second.suggested_button) {
1607      group = &iter->second;
1608      break;
1609    }
1610  }
1611  DCHECK(group);
1612
1613  if (!group->suggested_button->visible())
1614    return;
1615
1616  menu_runner_.reset(new views::MenuRunner(
1617                         delegate_->MenuModelForSection(group->section)));
1618
1619  group->container->SetActive(true);
1620  views::Button::ButtonState state = group->suggested_button->state();
1621  group->suggested_button->SetState(views::Button::STATE_PRESSED);
1622  // Ignore the result since we don't need to handle a deleted menu specially.
1623  gfx::Rect bounds = group->suggested_button->GetBoundsInScreen();
1624  bounds.Inset(group->suggested_button->GetInsets());
1625  ignore_result(
1626      menu_runner_->RunMenuAt(sender->GetWidget(),
1627                              NULL,
1628                              bounds,
1629                              views::MenuItemView::TOPRIGHT,
1630                              ui::GetMenuSourceTypeForEvent(event),
1631                              0));
1632  group->container->SetActive(false);
1633  group->suggested_button->SetState(state);
1634}
1635
1636void AutofillDialogViews::ContentsChanged(views::Textfield* sender,
1637                                          const base::string16& new_contents) {
1638  TextfieldEditedOrActivated(sender, true);
1639}
1640
1641bool AutofillDialogViews::HandleKeyEvent(views::Textfield* sender,
1642                                         const ui::KeyEvent& key_event) {
1643  scoped_ptr<ui::KeyEvent> copy(key_event.Copy());
1644#if defined(OS_WIN) && !defined(USE_AURA)
1645  content::NativeWebKeyboardEvent event(copy->native_event());
1646#else
1647  content::NativeWebKeyboardEvent event(copy.get());
1648#endif
1649  return delegate_->HandleKeyPressEventInInput(event);
1650}
1651
1652bool AutofillDialogViews::HandleMouseEvent(views::Textfield* sender,
1653                                           const ui::MouseEvent& mouse_event) {
1654  if (mouse_event.IsLeftMouseButton() && sender->HasFocus()) {
1655    TextfieldEditedOrActivated(sender, false);
1656    // Show an error bubble if a user clicks on an input that's already focused
1657    // (and invalid).
1658    ShowErrorBubbleForViewIfNecessary(sender);
1659  }
1660
1661  return false;
1662}
1663
1664void AutofillDialogViews::OnWillChangeFocus(
1665    views::View* focused_before,
1666    views::View* focused_now) {
1667  delegate_->FocusMoved();
1668  error_bubble_.reset();
1669}
1670
1671void AutofillDialogViews::OnDidChangeFocus(
1672    views::View* focused_before,
1673    views::View* focused_now) {
1674  // If user leaves an edit-field, revalidate the group it belongs to.
1675  if (focused_before) {
1676    DetailsGroup* group = GroupForView(focused_before);
1677    if (group && group->container->visible())
1678      ValidateGroup(*group, VALIDATE_EDIT);
1679  }
1680
1681  // Show an error bubble when the user focuses the input.
1682  if (focused_now)
1683    ShowErrorBubbleForViewIfNecessary(focused_now);
1684}
1685
1686void AutofillDialogViews::OnSelectedIndexChanged(views::Combobox* combobox) {
1687  DetailsGroup* group = GroupForView(combobox);
1688  ValidateGroup(*group, VALIDATE_EDIT);
1689}
1690
1691void AutofillDialogViews::StyledLabelLinkClicked(const ui::Range& range,
1692                                                 int event_flags) {
1693  delegate_->LegalDocumentLinkClicked(range);
1694}
1695
1696void AutofillDialogViews::InitChildViews() {
1697  button_strip_extra_view_ = new LayoutPropagationView();
1698  button_strip_extra_view_->SetLayoutManager(
1699      new views::BoxLayout(views::BoxLayout::kHorizontal, 0, 0, 0));
1700
1701  save_in_chrome_checkbox_container_ = new views::View();
1702  save_in_chrome_checkbox_container_->SetLayoutManager(
1703      new views::BoxLayout(views::BoxLayout::kHorizontal, 0, 0, 7));
1704  button_strip_extra_view_->AddChildView(save_in_chrome_checkbox_container_);
1705
1706  save_in_chrome_checkbox_ =
1707      new views::Checkbox(delegate_->SaveLocallyText());
1708  save_in_chrome_checkbox_->SetChecked(true);
1709  save_in_chrome_checkbox_container_->AddChildView(save_in_chrome_checkbox_);
1710
1711  save_in_chrome_checkbox_container_->AddChildView(
1712      new TooltipIcon(delegate_->SaveLocallyTooltip()));
1713
1714  button_strip_image_ = new views::ImageView();
1715  button_strip_extra_view_->AddChildView(button_strip_image_);
1716
1717  autocheckout_progress_bar_view_ = new views::View();
1718  views::GridLayout* progress_bar_layout =
1719      new views::GridLayout(autocheckout_progress_bar_view_);
1720  autocheckout_progress_bar_view_->SetLayoutManager(progress_bar_layout);
1721  const int kColumnSetId = 0;
1722  views::ColumnSet* columns = progress_bar_layout->AddColumnSet(kColumnSetId);
1723  columns->AddColumn(views::GridLayout::LEADING,
1724                     views::GridLayout::CENTER,
1725                     0,
1726                     views::GridLayout::USE_PREF,
1727                     0,
1728                     0);
1729  progress_bar_layout->StartRow(1.0, kColumnSetId);
1730
1731  autocheckout_progress_bar_ = new AutocheckoutProgressBar();
1732  progress_bar_layout->AddView(autocheckout_progress_bar_);
1733  button_strip_extra_view_->AddChildView(autocheckout_progress_bar_view_);
1734
1735  account_chooser_ = new AccountChooser(delegate_);
1736  notification_area_ = new NotificationArea(delegate_);
1737  notification_area_->set_arrow_centering_anchor(account_chooser_->AsWeakPtr());
1738  AddChildView(notification_area_);
1739
1740  scrollable_area_ = new views::ScrollView();
1741  scrollable_area_->set_hide_horizontal_scrollbar(true);
1742  scrollable_area_->SetContents(CreateDetailsContainer());
1743  AddChildView(scrollable_area_);
1744
1745  autocheckout_steps_area_ = new AutocheckoutStepsArea();
1746  AddChildView(autocheckout_steps_area_);
1747
1748  loading_shield_ = new views::Label();
1749  loading_shield_->SetVisible(false);
1750  loading_shield_->set_background(views::Background::CreateSolidBackground(
1751      GetNativeTheme()->GetSystemColor(
1752          ui::NativeTheme::kColorId_DialogBackground)));
1753  loading_shield_->SetFont(ui::ResourceBundle::GetSharedInstance().GetFont(
1754      ui::ResourceBundle::BaseFont).DeriveFont(15));
1755  AddChildView(loading_shield_);
1756
1757  sign_in_webview_ = new views::WebView(delegate_->profile());
1758  sign_in_webview_->SetVisible(false);
1759  AddChildView(sign_in_webview_);
1760  sign_in_delegate_.reset(
1761      new AutofillDialogSignInDelegate(this,
1762                                       sign_in_webview_->GetWebContents()));
1763
1764  overlay_view_ = new OverlayView(this);
1765  overlay_view_->SetVisible(false);
1766}
1767
1768views::View* AutofillDialogViews::CreateDetailsContainer() {
1769  details_container_ = new DetailsContainerView(
1770      base::Bind(&AutofillDialogViews::DetailsContainerBoundsChanged,
1771                 base::Unretained(this)));
1772  // A box layout is used because it respects widget visibility.
1773  details_container_->SetLayoutManager(
1774      new views::BoxLayout(views::BoxLayout::kVertical, 0, 0, 0));
1775  for (DetailGroupMap::iterator iter = detail_groups_.begin();
1776       iter != detail_groups_.end(); ++iter) {
1777    CreateDetailsSection(iter->second.section);
1778    details_container_->AddChildView(iter->second.container);
1779  }
1780
1781  return details_container_;
1782}
1783
1784void AutofillDialogViews::CreateDetailsSection(DialogSection section) {
1785  // Inputs container (manual inputs + combobox).
1786  views::View* inputs_container = CreateInputsContainer(section);
1787
1788  DetailsGroup* group = GroupForSection(section);
1789  // Container (holds label + inputs).
1790  group->container = new SectionContainer(
1791      delegate_->LabelForSection(section),
1792      inputs_container,
1793      group->suggested_button);
1794  DCHECK(group->suggested_button->parent());
1795  UpdateDetailsGroupState(*group);
1796}
1797
1798views::View* AutofillDialogViews::CreateInputsContainer(DialogSection section) {
1799  // The |info_view| holds |manual_inputs| and |suggested_info|, allowing the
1800  // dialog to toggle which is shown.
1801  views::View* info_view = new views::View();
1802  info_view->SetLayoutManager(
1803      new views::BoxLayout(views::BoxLayout::kVertical, 0, 0, 0));
1804
1805  views::View* manual_inputs = InitInputsView(section);
1806  info_view->AddChildView(manual_inputs);
1807  SuggestionView* suggested_info =
1808      new SuggestionView(delegate_->EditSuggestionText(), this);
1809  info_view->AddChildView(suggested_info);
1810
1811  // TODO(estade): It might be slightly more OO if this button were created
1812  // and listened to by the section container.
1813  views::ImageButton* menu_button = new views::ImageButton(this);
1814  ui::ResourceBundle& rb = ui::ResourceBundle::GetSharedInstance();
1815  menu_button->SetImage(views::Button::STATE_NORMAL,
1816      rb.GetImageSkiaNamed(IDR_AUTOFILL_DIALOG_MENU_BUTTON));
1817  menu_button->SetImage(views::Button::STATE_PRESSED,
1818      rb.GetImageSkiaNamed(IDR_AUTOFILL_DIALOG_MENU_BUTTON_P));
1819  menu_button->SetImage(views::Button::STATE_HOVERED,
1820      rb.GetImageSkiaNamed(IDR_AUTOFILL_DIALOG_MENU_BUTTON_H));
1821  menu_button->SetImage(views::Button::STATE_DISABLED,
1822      rb.GetImageSkiaNamed(IDR_AUTOFILL_DIALOG_MENU_BUTTON_D));
1823  menu_button->set_border(views::Border::CreateEmptyBorder(
1824      kMenuButtonTopInset,
1825      kDialogEdgePadding,
1826      kMenuButtonBottomInset,
1827      0));
1828
1829  DetailsGroup* group = GroupForSection(section);
1830  group->suggested_button = menu_button;
1831  group->manual_input = manual_inputs;
1832  group->suggested_info = suggested_info;
1833  return info_view;
1834}
1835
1836// TODO(estade): we should be using Chrome-style constrained window padding
1837// values.
1838views::View* AutofillDialogViews::InitInputsView(DialogSection section) {
1839  const DetailInputs& inputs = delegate_->RequestedFieldsForSection(section);
1840  TextfieldMap* textfields = &GroupForSection(section)->textfields;
1841  ComboboxMap* comboboxes = &GroupForSection(section)->comboboxes;
1842
1843  views::View* view = new views::View();
1844  views::GridLayout* layout = new views::GridLayout(view);
1845  view->SetLayoutManager(layout);
1846
1847  for (DetailInputs::const_iterator it = inputs.begin();
1848       it != inputs.end(); ++it) {
1849    const DetailInput& input = *it;
1850    ui::ComboboxModel* input_model =
1851        delegate_->ComboboxModelForAutofillType(input.type);
1852    scoped_ptr<views::View> view_to_add;
1853    if (input_model) {
1854      views::Combobox* combobox = new views::Combobox(input_model);
1855      combobox->set_listener(this);
1856      comboboxes->insert(std::make_pair(&input, combobox));
1857
1858      for (int i = 0; i < input_model->GetItemCount(); ++i) {
1859        if (input.initial_value == input_model->GetItemAt(i)) {
1860          combobox->SetSelectedIndex(i);
1861          break;
1862        }
1863      }
1864
1865      view_to_add.reset(combobox);
1866    } else {
1867      DecoratedTextfield* field = new DecoratedTextfield(
1868          input.initial_value,
1869          l10n_util::GetStringUTF16(input.placeholder_text_rid),
1870          this);
1871
1872      gfx::Image icon =
1873          delegate_->IconForField(input.type, input.initial_value);
1874      field->SetIcon(icon);
1875
1876      textfields->insert(std::make_pair(&input, field));
1877      view_to_add.reset(field);
1878    }
1879
1880    int kColumnSetId = input.row_id;
1881    if (kColumnSetId < 0) {
1882      other_owned_views_.push_back(view_to_add.release());
1883      continue;
1884    }
1885
1886    views::ColumnSet* column_set = layout->GetColumnSet(kColumnSetId);
1887    if (!column_set) {
1888      // Create a new column set and row.
1889      column_set = layout->AddColumnSet(kColumnSetId);
1890      if (it != inputs.begin())
1891        layout->AddPaddingRow(0, views::kRelatedControlVerticalSpacing);
1892      layout->StartRow(0, kColumnSetId);
1893    } else {
1894      // Add a new column to existing row.
1895      column_set->AddPaddingColumn(0, views::kRelatedControlHorizontalSpacing);
1896      // Must explicitly skip the padding column since we've already started
1897      // adding views.
1898      layout->SkipColumns(1);
1899    }
1900
1901    float expand = input.expand_weight;
1902    column_set->AddColumn(views::GridLayout::FILL,
1903                          views::GridLayout::FILL,
1904                          expand ? expand : 1.0,
1905                          views::GridLayout::USE_PREF,
1906                          0,
1907                          0);
1908
1909    // This is the same as AddView(view_to_add), except that 1 is used for the
1910    // view's preferred width. Thus the width of the column completely depends
1911    // on |expand|.
1912    layout->AddView(view_to_add.release(), 1, 1,
1913                    views::GridLayout::FILL, views::GridLayout::FILL,
1914                    1, 0);
1915  }
1916
1917  return view;
1918}
1919
1920void AutofillDialogViews::UpdateSectionImpl(
1921    DialogSection section,
1922    bool clobber_inputs) {
1923  const DetailInputs& updated_inputs =
1924      delegate_->RequestedFieldsForSection(section);
1925  DetailsGroup* group = GroupForSection(section);
1926
1927  for (DetailInputs::const_iterator iter = updated_inputs.begin();
1928       iter != updated_inputs.end(); ++iter) {
1929    const DetailInput& input = *iter;
1930    TextfieldMap::iterator text_mapping = group->textfields.find(&input);
1931
1932    if (text_mapping != group->textfields.end()) {
1933      DecoratedTextfield* decorated = text_mapping->second;
1934      decorated->SetEnabled(input.editable);
1935      if (decorated->text().empty() || clobber_inputs) {
1936        decorated->SetText(iter->initial_value);
1937        decorated->SetIcon(
1938            delegate_->IconForField(input.type, decorated->text()));
1939      }
1940    }
1941
1942    ComboboxMap::iterator combo_mapping = group->comboboxes.find(&input);
1943    if (combo_mapping != group->comboboxes.end()) {
1944      views::Combobox* combobox = combo_mapping->second;
1945      combobox->SetEnabled(input.editable);
1946      if (combobox->selected_index() == combobox->model()->GetDefaultIndex() ||
1947          clobber_inputs) {
1948        for (int i = 0; i < combobox->model()->GetItemCount(); ++i) {
1949          if (input.initial_value == combobox->model()->GetItemAt(i)) {
1950            combobox->SetSelectedIndex(i);
1951            break;
1952          }
1953        }
1954      }
1955    }
1956  }
1957
1958  UpdateDetailsGroupState(*group);
1959}
1960
1961void AutofillDialogViews::UpdateDetailsGroupState(const DetailsGroup& group) {
1962  const SuggestionState& suggestion_state =
1963      delegate_->SuggestionStateForSection(group.section);
1964  bool show_suggestions = !suggestion_state.text.empty();
1965  group.suggested_info->SetVisible(show_suggestions);
1966  group.suggested_info->SetSuggestionText(suggestion_state.text,
1967                                          suggestion_state.text_style);
1968  group.suggested_info->SetSuggestionIcon(suggestion_state.icon);
1969
1970  if (!suggestion_state.extra_text.empty()) {
1971    group.suggested_info->ShowTextfield(
1972        suggestion_state.extra_text,
1973        suggestion_state.extra_icon);
1974  }
1975
1976  group.manual_input->SetVisible(!show_suggestions);
1977
1978  UpdateButtonStripExtraView();
1979
1980  const bool has_menu = !!delegate_->MenuModelForSection(group.section);
1981
1982  if (group.suggested_button)
1983    group.suggested_button->SetVisible(has_menu);
1984
1985  if (group.container) {
1986    group.container->SetForwardMouseEvents(has_menu && show_suggestions);
1987    group.container->SetVisible(delegate_->SectionIsActive(group.section));
1988    if (group.container->visible())
1989      ValidateGroup(group, VALIDATE_EDIT);
1990  }
1991
1992  ContentsPreferredSizeChanged();
1993}
1994
1995template<class T>
1996void AutofillDialogViews::SetValidityForInput(
1997    T* input,
1998    const base::string16& message) {
1999  bool invalid = !message.empty();
2000  input->SetInvalid(invalid);
2001
2002  if (invalid) {
2003    validity_map_[input] = message;
2004  } else {
2005    validity_map_.erase(input);
2006
2007    if (error_bubble_ && error_bubble_->anchor() == input) {
2008      validity_map_.erase(input);
2009      error_bubble_.reset();
2010    }
2011  }
2012}
2013
2014void AutofillDialogViews::ShowErrorBubbleForViewIfNecessary(views::View* view) {
2015  if (!view->GetWidget())
2016    return;
2017
2018  if (error_bubble_ && error_bubble_->anchor() == view)
2019    return;
2020
2021  std::map<views::View*, base::string16>::iterator error_message =
2022      validity_map_.find(view);
2023  if (error_message != validity_map_.end()) {
2024    view->ScrollRectToVisible(view->GetLocalBounds());
2025    error_bubble_.reset(new ErrorBubble(view, error_message->second));
2026  }
2027}
2028
2029void AutofillDialogViews::MarkInputsInvalid(DialogSection section,
2030                                            const ValidityData& validity_data) {
2031  DetailsGroup* group = GroupForSection(section);
2032  DCHECK(group->container->visible());
2033
2034  typedef std::map<ServerFieldType,
2035      base::Callback<void(const base::string16&)> > FieldMap;
2036  FieldMap field_map;
2037
2038  if (group->manual_input->visible()) {
2039    for (TextfieldMap::const_iterator iter = group->textfields.begin();
2040         iter != group->textfields.end(); ++iter) {
2041      field_map[iter->first->type] = base::Bind(
2042          &AutofillDialogViews::SetValidityForInput<DecoratedTextfield>,
2043          base::Unretained(this),
2044          iter->second);
2045    }
2046    for (ComboboxMap::const_iterator iter = group->comboboxes.begin();
2047         iter != group->comboboxes.end(); ++iter) {
2048      field_map[iter->first->type] = base::Bind(
2049          &AutofillDialogViews::SetValidityForInput<views::Combobox>,
2050          base::Unretained(this),
2051          iter->second);
2052    }
2053  } else {
2054    // Purge invisible views from |validity_map_|.
2055    std::map<views::View*, base::string16>::iterator it;
2056    for (it = validity_map_.begin(); it != validity_map_.end();) {
2057      DCHECK(GroupForView(it->first));
2058      if (GroupForView(it->first) == group)
2059        validity_map_.erase(it++);
2060      else
2061        ++it;
2062    }
2063
2064    if (section == SECTION_CC) {
2065      // Special case CVC as it's not part of |group->manual_input|.
2066      field_map[CREDIT_CARD_VERIFICATION_CODE] = base::Bind(
2067          &AutofillDialogViews::SetValidityForInput<DecoratedTextfield>,
2068          base::Unretained(this),
2069          group->suggested_info->decorated_textfield());
2070    }
2071  }
2072
2073  // Flag invalid fields, removing them from |field_map|.
2074  for (ValidityData::const_iterator iter = validity_data.begin();
2075       iter != validity_data.end(); ++iter) {
2076    const base::string16& message = iter->second;
2077    field_map[iter->first].Run(message);
2078    field_map.erase(iter->first);
2079  }
2080
2081  // The remaining fields in |field_map| are valid. Mark them as such.
2082  for (FieldMap::iterator iter = field_map.begin(); iter != field_map.end();
2083       ++iter) {
2084    iter->second.Run(base::string16());
2085  }
2086}
2087
2088bool AutofillDialogViews::ValidateGroup(const DetailsGroup& group,
2089                                        ValidationType validation_type) {
2090  DCHECK(group.container->visible());
2091
2092  scoped_ptr<DetailInput> cvc_input;
2093  DetailOutputMap detail_outputs;
2094
2095  if (group.manual_input->visible()) {
2096    for (TextfieldMap::const_iterator iter = group.textfields.begin();
2097         iter != group.textfields.end(); ++iter) {
2098      if (!iter->first->editable)
2099        continue;
2100
2101      detail_outputs[iter->first] = iter->second->text();
2102    }
2103    for (ComboboxMap::const_iterator iter = group.comboboxes.begin();
2104         iter != group.comboboxes.end(); ++iter) {
2105      if (!iter->first->editable)
2106        continue;
2107
2108      views::Combobox* combobox = iter->second;
2109      base::string16 item =
2110          combobox->model()->GetItemAt(combobox->selected_index());
2111      detail_outputs[iter->first] = item;
2112    }
2113  } else if (group.section == SECTION_CC) {
2114    DecoratedTextfield* decorated_cvc =
2115        group.suggested_info->decorated_textfield();
2116    cvc_input.reset(new DetailInput);
2117    cvc_input->type = CREDIT_CARD_VERIFICATION_CODE;
2118    detail_outputs[cvc_input.get()] = decorated_cvc->text();
2119  }
2120
2121  ValidityData invalid_inputs = delegate_->InputsAreValid(
2122      group.section, detail_outputs, validation_type);
2123  MarkInputsInvalid(group.section, invalid_inputs);
2124
2125  return invalid_inputs.empty();
2126}
2127
2128bool AutofillDialogViews::ValidateForm() {
2129  bool all_valid = true;
2130  validity_map_.clear();
2131
2132  for (DetailGroupMap::iterator iter = detail_groups_.begin();
2133       iter != detail_groups_.end(); ++iter) {
2134    const DetailsGroup& group = iter->second;
2135    if (!group.container->visible())
2136      continue;
2137
2138    if (!ValidateGroup(group, VALIDATE_FINAL))
2139      all_valid = false;
2140  }
2141
2142  return all_valid;
2143}
2144
2145void AutofillDialogViews::TextfieldEditedOrActivated(
2146    views::Textfield* textfield,
2147    bool was_edit) {
2148  DetailsGroup* group = GroupForView(textfield);
2149  DCHECK(group);
2150
2151  // Figure out the ServerFieldType this textfield represents.
2152  ServerFieldType type = UNKNOWN_TYPE;
2153  DecoratedTextfield* decorated = NULL;
2154
2155  // Look for the input in the manual inputs.
2156  for (TextfieldMap::const_iterator iter = group->textfields.begin();
2157       iter != group->textfields.end();
2158       ++iter) {
2159    decorated = iter->second;
2160    if (decorated == textfield) {
2161      delegate_->UserEditedOrActivatedInput(group->section,
2162                                              iter->first,
2163                                              GetWidget()->GetNativeView(),
2164                                              textfield->GetBoundsInScreen(),
2165                                              textfield->text(),
2166                                              was_edit);
2167      type = iter->first->type;
2168      break;
2169    }
2170  }
2171
2172  if (textfield == group->suggested_info->decorated_textfield()) {
2173    decorated = group->suggested_info->decorated_textfield();
2174    type = CREDIT_CARD_VERIFICATION_CODE;
2175  }
2176  DCHECK_NE(UNKNOWN_TYPE, type);
2177
2178  // If the field is marked as invalid, check if the text is now valid.
2179  // Many fields (i.e. CC#) are invalid for most of the duration of editing,
2180  // so flagging them as invalid prematurely is not helpful. However,
2181  // correcting a minor mistake (i.e. a wrong CC digit) should immediately
2182  // result in validation - positive user feedback.
2183  if (decorated->invalid() && was_edit) {
2184    SetValidityForInput<DecoratedTextfield>(
2185        decorated,
2186        delegate_->InputValidityMessage(group->section, type,
2187                                          textfield->text()));
2188
2189    // If the field transitioned from invalid to valid, re-validate the group,
2190    // since inter-field checks become meaningful with valid fields.
2191    if (!decorated->invalid())
2192      ValidateGroup(*group, VALIDATE_EDIT);
2193  }
2194
2195  gfx::Image icon = delegate_->IconForField(type, textfield->text());
2196  decorated->SetIcon(icon);
2197}
2198
2199void AutofillDialogViews::UpdateButtonStripExtraView() {
2200  save_in_chrome_checkbox_container_->SetVisible(
2201      delegate_->ShouldOfferToSaveInChrome());
2202
2203  gfx::Image image = delegate_->ButtonStripImage();
2204  button_strip_image_->SetVisible(!image.IsEmpty());
2205  button_strip_image_->SetImage(image.AsImageSkia());
2206
2207  autocheckout_progress_bar_view_->SetVisible(
2208      delegate_->ShouldShowProgressBar());
2209}
2210
2211void AutofillDialogViews::ContentsPreferredSizeChanged() {
2212  if (GetWidget()) {
2213    GetWidget()->SetSize(GetWidget()->non_client_view()->GetPreferredSize());
2214    // If the above line does not cause the dialog's size to change, |contents_|
2215    // may not be laid out. This will trigger a layout only if it's needed.
2216    SetBoundsRect(bounds());
2217  }
2218}
2219
2220AutofillDialogViews::DetailsGroup* AutofillDialogViews::GroupForSection(
2221    DialogSection section) {
2222  return &detail_groups_.find(section)->second;
2223}
2224
2225AutofillDialogViews::DetailsGroup* AutofillDialogViews::GroupForView(
2226    views::View* view) {
2227  DCHECK(view);
2228
2229  for (DetailGroupMap::iterator iter = detail_groups_.begin();
2230       iter != detail_groups_.end(); ++iter) {
2231    DetailsGroup* group = &iter->second;
2232    if (view->parent() == group->manual_input)
2233      return group;
2234
2235    views::View* decorated =
2236        view->GetAncestorWithClassName(DecoratedTextfield::kViewClassName);
2237
2238    // Textfields need to check a second case, since they can be
2239    // suggested inputs instead of directly editable inputs. Those are
2240    // accessed via |suggested_info|.
2241    if (decorated &&
2242        decorated == group->suggested_info->decorated_textfield()) {
2243      return group;
2244    }
2245  }
2246  return NULL;
2247}
2248
2249views::Textfield* AutofillDialogViews::TextfieldForInput(
2250    const DetailInput& input) {
2251  for (DetailGroupMap::iterator iter = detail_groups_.begin();
2252       iter != detail_groups_.end(); ++iter) {
2253    const DetailsGroup& group = iter->second;
2254    TextfieldMap::const_iterator text_mapping = group.textfields.find(&input);
2255    if (text_mapping != group.textfields.end())
2256      return text_mapping->second;
2257  }
2258
2259  return NULL;
2260}
2261
2262views::Combobox* AutofillDialogViews::ComboboxForInput(
2263    const DetailInput& input) {
2264  for (DetailGroupMap::iterator iter = detail_groups_.begin();
2265       iter != detail_groups_.end(); ++iter) {
2266    const DetailsGroup& group = iter->second;
2267    ComboboxMap::const_iterator combo_mapping = group.comboboxes.find(&input);
2268    if (combo_mapping != group.comboboxes.end())
2269      return combo_mapping->second;
2270  }
2271
2272  return NULL;
2273}
2274
2275void AutofillDialogViews::DetailsContainerBoundsChanged() {
2276  if (error_bubble_)
2277    error_bubble_->UpdatePosition();
2278}
2279
2280AutofillDialogViews::DetailsGroup::DetailsGroup(DialogSection section)
2281    : section(section),
2282      container(NULL),
2283      manual_input(NULL),
2284      suggested_info(NULL),
2285      suggested_button(NULL) {}
2286
2287AutofillDialogViews::DetailsGroup::~DetailsGroup() {}
2288
2289}  // namespace autofill
2290