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