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