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