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