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