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