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