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