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