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