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