[Metrics] Make MetricsStateManager take a callback param to check if UMA is enabled.
[chromium-blink-merge.git] / chrome / browser / ui / views / autofill / autofill_dialog_views.cc
blob4c1ae58735d4b8d0a156587f1568c55bfe8b9764
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.
5 #include "chrome/browser/ui/views/autofill/autofill_dialog_views.h"
7 #include <utility>
9 #include "base/bind.h"
10 #include "base/location.h"
11 #include "base/strings/utf_string_conversions.h"
12 #include "chrome/browser/profiles/profile.h"
13 #include "chrome/browser/ui/autofill/autofill_dialog_sign_in_delegate.h"
14 #include "chrome/browser/ui/autofill/autofill_dialog_view_delegate.h"
15 #include "chrome/browser/ui/autofill/loading_animation.h"
16 #include "chrome/browser/ui/views/autofill/expanding_textfield.h"
17 #include "chrome/browser/ui/views/autofill/info_bubble.h"
18 #include "chrome/browser/ui/views/autofill/tooltip_icon.h"
19 #include "chrome/browser/ui/views/constrained_window_views.h"
20 #include "components/autofill/content/browser/wallet/wallet_service_url.h"
21 #include "components/autofill/core/browser/autofill_type.h"
22 #include "components/web_modal/web_contents_modal_dialog_host.h"
23 #include "components/web_modal/web_contents_modal_dialog_manager.h"
24 #include "components/web_modal/web_contents_modal_dialog_manager_delegate.h"
25 #include "content/public/browser/native_web_keyboard_event.h"
26 #include "content/public/browser/navigation_controller.h"
27 #include "content/public/browser/web_contents.h"
28 #include "grit/theme_resources.h"
29 #include "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/events/event_handler.h"
36 #include "ui/gfx/animation/animation_delegate.h"
37 #include "ui/gfx/canvas.h"
38 #include "ui/gfx/color_utils.h"
39 #include "ui/gfx/font_list.h"
40 #include "ui/gfx/path.h"
41 #include "ui/gfx/point.h"
42 #include "ui/gfx/skia_util.h"
43 #include "ui/views/background.h"
44 #include "ui/views/border.h"
45 #include "ui/views/bubble/bubble_border.h"
46 #include "ui/views/bubble/bubble_frame_view.h"
47 #include "ui/views/controls/button/blue_button.h"
48 #include "ui/views/controls/button/checkbox.h"
49 #include "ui/views/controls/button/label_button.h"
50 #include "ui/views/controls/button/label_button_border.h"
51 #include "ui/views/controls/button/menu_button.h"
52 #include "ui/views/controls/combobox/combobox.h"
53 #include "ui/views/controls/image_view.h"
54 #include "ui/views/controls/label.h"
55 #include "ui/views/controls/link.h"
56 #include "ui/views/controls/menu/menu_runner.h"
57 #include "ui/views/controls/separator.h"
58 #include "ui/views/controls/styled_label.h"
59 #include "ui/views/controls/textfield/textfield.h"
60 #include "ui/views/controls/webview/webview.h"
61 #include "ui/views/layout/box_layout.h"
62 #include "ui/views/layout/fill_layout.h"
63 #include "ui/views/layout/grid_layout.h"
64 #include "ui/views/layout/layout_constants.h"
65 #include "ui/views/painter.h"
66 #include "ui/views/widget/widget.h"
67 #include "ui/views/window/dialog_client_view.h"
68 #include "ui/views/window/non_client_view.h"
70 using web_modal::WebContentsModalDialogManager;
71 using web_modal::WebContentsModalDialogManagerDelegate;
73 namespace autofill {
75 namespace {
77 // The width for the section container.
78 const int kSectionContainerWidth = 440;
80 // The minimum useful height of the contents area of the dialog.
81 const int kMinimumContentsHeight = 101;
83 // The default height of the loading shield, also its minimum size.
84 const int kInitialLoadingShieldHeight = 150;
86 // Horizontal padding between text and other elements (in pixels).
87 const int kAroundTextPadding = 4;
89 // The space between the edges of a notification bar and the text within (in
90 // pixels).
91 const int kNotificationPadding = 17;
93 // Vertical padding above and below each detail section (in pixels).
94 const int kDetailSectionVerticalPadding = 10;
96 const int kArrowHeight = 7;
97 const int kArrowWidth = 2 * kArrowHeight;
99 // The padding inside the edges of the dialog, in pixels.
100 const int kDialogEdgePadding = 20;
102 // The vertical padding between rows of manual inputs (in pixels).
103 const int kManualInputRowPadding = 10;
105 // Slight shading for mouse hover and legal document background.
106 SkColor kShadingColor = SkColorSetARGB(7, 0, 0, 0);
108 // A border color for the legal document view.
109 SkColor kSubtleBorderColor = SkColorSetARGB(10, 0, 0, 0);
111 // The top and bottom padding, in pixels, for the suggestions menu dropdown
112 // arrows.
113 const int kMenuButtonTopInset = 3;
114 const int kMenuButtonBottomInset = 6;
116 // The height in pixels of the padding above and below the overlay message view.
117 const int kOverlayMessageVerticalPadding = 34;
119 // Spacing below image and above text messages in overlay view.
120 const int kOverlayImageBottomMargin = 100;
122 const char kNotificationAreaClassName[] = "autofill/NotificationArea";
123 const char kOverlayViewClassName[] = "autofill/OverlayView";
124 const char kSectionContainerClassName[] = "autofill/SectionContainer";
125 const char kSuggestedButtonClassName[] = "autofill/SuggestedButton";
127 // Draws an arrow at the top of |canvas| pointing to |tip_x|.
128 void DrawArrow(gfx::Canvas* canvas,
129 int tip_x,
130 const SkColor& fill_color,
131 const SkColor& stroke_color) {
132 const int arrow_half_width = kArrowWidth / 2.0f;
134 SkPath arrow;
135 arrow.moveTo(tip_x - arrow_half_width, kArrowHeight);
136 arrow.lineTo(tip_x, 0);
137 arrow.lineTo(tip_x + arrow_half_width, kArrowHeight);
139 SkPaint fill_paint;
140 fill_paint.setColor(fill_color);
141 canvas->DrawPath(arrow, fill_paint);
143 if (stroke_color != SK_ColorTRANSPARENT) {
144 SkPaint stroke_paint;
145 stroke_paint.setColor(stroke_color);
146 stroke_paint.setStyle(SkPaint::kStroke_Style);
147 canvas->DrawPath(arrow, stroke_paint);
151 void SelectComboboxValueOrSetToDefault(views::Combobox* combobox,
152 const base::string16& value) {
153 if (!combobox->SelectValue(value))
154 combobox->SetSelectedIndex(combobox->model()->GetDefaultIndex());
157 // This class handles layout for the first row of a SuggestionView.
158 // It exists to circumvent shortcomings of GridLayout and BoxLayout (namely that
159 // the former doesn't fully respect child visibility, and that the latter won't
160 // expand a single child).
161 class SectionRowView : public views::View {
162 public:
163 SectionRowView() { SetBorder(views::Border::CreateEmptyBorder(10, 0, 0, 0)); }
165 virtual ~SectionRowView() {}
167 // views::View implementation:
168 virtual gfx::Size GetPreferredSize() const OVERRIDE {
169 int height = 0;
170 int width = 0;
171 for (int i = 0; i < child_count(); ++i) {
172 if (child_at(i)->visible()) {
173 if (width > 0)
174 width += kAroundTextPadding;
176 gfx::Size size = child_at(i)->GetPreferredSize();
177 height = std::max(height, size.height());
178 width += size.width();
182 gfx::Insets insets = GetInsets();
183 return gfx::Size(width + insets.width(), height + insets.height());
186 virtual void Layout() OVERRIDE {
187 const gfx::Rect bounds = GetContentsBounds();
189 // Icon is left aligned.
190 int start_x = bounds.x();
191 views::View* icon = child_at(0);
192 if (icon->visible()) {
193 icon->SizeToPreferredSize();
194 icon->SetX(start_x);
195 icon->SetY(bounds.y() +
196 (bounds.height() - icon->bounds().height()) / 2);
197 start_x += icon->bounds().width() + kAroundTextPadding;
200 // Textfield is right aligned.
201 int end_x = bounds.width();
202 views::View* textfield = child_at(2);
203 if (textfield->visible()) {
204 const int preferred_width = textfield->GetPreferredSize().width();
205 textfield->SetBounds(bounds.width() - preferred_width, bounds.y(),
206 preferred_width, bounds.height());
207 end_x = textfield->bounds().x() - kAroundTextPadding;
210 // Label takes up all the space in between.
211 views::View* label = child_at(1);
212 if (label->visible())
213 label->SetBounds(start_x, bounds.y(), end_x - start_x, bounds.height());
215 views::View::Layout();
218 private:
219 DISALLOW_COPY_AND_ASSIGN(SectionRowView);
222 // A view that propagates visibility and preferred size changes.
223 class LayoutPropagationView : public views::View {
224 public:
225 LayoutPropagationView() {}
226 virtual ~LayoutPropagationView() {}
228 protected:
229 virtual void ChildVisibilityChanged(views::View* child) OVERRIDE {
230 PreferredSizeChanged();
232 virtual void ChildPreferredSizeChanged(views::View* child) OVERRIDE {
233 PreferredSizeChanged();
236 private:
237 DISALLOW_COPY_AND_ASSIGN(LayoutPropagationView);
240 // A View for a single notification banner.
241 class NotificationView : public views::View,
242 public views::ButtonListener,
243 public views::StyledLabelListener {
244 public:
245 NotificationView(const DialogNotification& data,
246 AutofillDialogViewDelegate* delegate)
247 : data_(data),
248 delegate_(delegate),
249 checkbox_(NULL) {
250 scoped_ptr<views::View> label_view;
251 if (data.HasCheckbox()) {
252 scoped_ptr<views::Checkbox> checkbox(
253 new views::Checkbox(base::string16()));
254 checkbox->SetText(data.display_text());
255 checkbox->SetTextMultiLine(true);
256 checkbox->SetHorizontalAlignment(gfx::ALIGN_LEFT);
257 checkbox->SetTextColor(views::Button::STATE_NORMAL,
258 data.GetTextColor());
259 checkbox->SetTextColor(views::Button::STATE_HOVERED,
260 data.GetTextColor());
261 checkbox->SetChecked(data.checked());
262 checkbox->set_listener(this);
263 checkbox_ = checkbox.get();
264 label_view.reset(checkbox.release());
265 } else {
266 scoped_ptr<views::StyledLabel> label(new views::StyledLabel(
267 data.display_text(), this));
268 label->set_auto_color_readability_enabled(false);
270 views::StyledLabel::RangeStyleInfo text_style;
271 text_style.color = data.GetTextColor();
273 if (data.link_range().is_empty()) {
274 label->AddStyleRange(gfx::Range(0, data.display_text().size()),
275 text_style);
276 } else {
277 gfx::Range prefix_range(0, data.link_range().start());
278 if (!prefix_range.is_empty())
279 label->AddStyleRange(prefix_range, text_style);
281 label->AddStyleRange(
282 data.link_range(),
283 views::StyledLabel::RangeStyleInfo::CreateForLink());
285 gfx::Range suffix_range(data.link_range().end(),
286 data.display_text().size());
287 if (!suffix_range.is_empty())
288 label->AddStyleRange(suffix_range, text_style);
291 label_view.reset(label.release());
294 AddChildView(label_view.release());
296 if (!data.tooltip_text().empty())
297 AddChildView(new TooltipIcon(data.tooltip_text()));
299 set_background(
300 views::Background::CreateSolidBackground(data.GetBackgroundColor()));
301 SetBorder(views::Border::CreateSolidSidedBorder(
302 1, 0, 1, 0, data.GetBorderColor()));
305 virtual ~NotificationView() {}
307 views::Checkbox* checkbox() {
308 return checkbox_;
311 // views::View implementation.
312 virtual gfx::Insets GetInsets() const OVERRIDE {
313 int vertical_padding = kNotificationPadding;
314 if (checkbox_)
315 vertical_padding -= 3;
316 return gfx::Insets(vertical_padding, kDialogEdgePadding,
317 vertical_padding, kDialogEdgePadding);
320 virtual int GetHeightForWidth(int width) const OVERRIDE {
321 int label_width = width - GetInsets().width();
322 if (child_count() > 1) {
323 const views::View* tooltip_icon = child_at(1);
324 label_width -= tooltip_icon->GetPreferredSize().width() +
325 kDialogEdgePadding;
328 return child_at(0)->GetHeightForWidth(label_width) + GetInsets().height();
331 virtual void Layout() OVERRIDE {
332 // Surprisingly, GetContentsBounds() doesn't consult GetInsets().
333 gfx::Rect bounds = GetLocalBounds();
334 bounds.Inset(GetInsets());
335 int right_bound = bounds.right();
337 if (child_count() > 1) {
338 // The icon takes up the entire vertical space and an extra 20px on
339 // each side. This increases the hover target for the tooltip.
340 views::View* tooltip_icon = child_at(1);
341 gfx::Size icon_size = tooltip_icon->GetPreferredSize();
342 int icon_width = icon_size.width() + kDialogEdgePadding;
343 right_bound -= icon_width;
344 tooltip_icon->SetBounds(
345 right_bound, 0,
346 icon_width + kDialogEdgePadding, GetLocalBounds().height());
349 child_at(0)->SetBounds(bounds.x(), bounds.y(),
350 right_bound - bounds.x(), bounds.height());
353 // views::ButtonListener implementation.
354 virtual void ButtonPressed(views::Button* sender,
355 const ui::Event& event) OVERRIDE {
356 DCHECK_EQ(sender, checkbox_);
357 delegate_->NotificationCheckboxStateChanged(data_.type(),
358 checkbox_->checked());
361 // views::StyledLabelListener implementation.
362 virtual void StyledLabelLinkClicked(const gfx::Range& range, int event_flags)
363 OVERRIDE {
364 delegate_->LinkClicked(data_.link_url());
367 private:
368 // The model data for this notification.
369 DialogNotification data_;
371 // The delegate that handles interaction with |this|.
372 AutofillDialogViewDelegate* delegate_;
374 // The checkbox associated with this notification, or NULL if there is none.
375 views::Checkbox* checkbox_;
377 DISALLOW_COPY_AND_ASSIGN(NotificationView);
380 // A view that displays a loading message with some dancing dots.
381 class LoadingAnimationView : public views::View,
382 public gfx::AnimationDelegate {
383 public:
384 explicit LoadingAnimationView(const base::string16& text) :
385 container_(new views::View()) {
386 AddChildView(container_);
387 container_->SetLayoutManager(
388 new views::BoxLayout(views::BoxLayout::kHorizontal, 0, 0, 0));
390 const gfx::FontList& font_list =
391 ui::ResourceBundle::GetSharedInstance().GetFontList(
392 ui::ResourceBundle::LargeFont);
393 animation_.reset(new LoadingAnimation(this, font_list.GetHeight()));
395 container_->AddChildView(new views::Label(text, font_list));
397 for (size_t i = 0; i < 3; ++i) {
398 container_->AddChildView(
399 new views::Label(base::ASCIIToUTF16("."), font_list));
403 virtual ~LoadingAnimationView() {}
405 // views::View implementation.
406 virtual void SetVisible(bool visible) OVERRIDE {
407 if (visible)
408 animation_->Start();
409 else
410 animation_->Reset();
412 views::View::SetVisible(visible);
415 virtual void Layout() OVERRIDE {
416 gfx::Size container_size = container_->GetPreferredSize();
417 gfx::Rect container_bounds((width() - container_size.width()) / 2,
418 (height() - container_size.height()) / 2,
419 container_size.width(),
420 container_size.height());
421 container_->SetBoundsRect(container_bounds);
422 container_->Layout();
424 for (size_t i = 0; i < 3; ++i) {
425 views::View* dot = container_->child_at(i + 1);
426 dot->SetY(dot->y() + animation_->GetCurrentValueForDot(i));
430 virtual void OnNativeThemeChanged(const ui::NativeTheme* theme) OVERRIDE {
431 set_background(views::Background::CreateSolidBackground(
432 theme->GetSystemColor(ui::NativeTheme::kColorId_DialogBackground)));
435 // gfx::AnimationDelegate implementation.
436 virtual void AnimationProgressed(const gfx::Animation* animation) OVERRIDE {
437 DCHECK_EQ(animation, animation_.get());
438 Layout();
441 private:
442 // Contains the "Loading" label and the dots.
443 views::View* container_;
445 scoped_ptr<LoadingAnimation> animation_;
447 DISALLOW_COPY_AND_ASSIGN(LoadingAnimationView);
450 // Gets either the Combobox or ExpandingTextfield that is an ancestor (including
451 // self) of |view|.
452 views::View* GetAncestralInputView(views::View* view) {
453 if (view->GetClassName() == views::Combobox::kViewClassName)
454 return view;
456 return view->GetAncestorWithClassName(ExpandingTextfield::kViewClassName);
459 // A class that informs |delegate_| when an unhandled mouse press occurs.
460 class MousePressedHandler : public ui::EventHandler {
461 public:
462 explicit MousePressedHandler(AutofillDialogViewDelegate* delegate)
463 : delegate_(delegate) {}
465 // ui::EventHandler implementation.
466 virtual void OnMouseEvent(ui::MouseEvent* event) OVERRIDE {
467 if (event->type() == ui::ET_MOUSE_PRESSED && !event->handled())
468 delegate_->FocusMoved();
471 private:
472 AutofillDialogViewDelegate* const delegate_;
474 DISALLOW_COPY_AND_ASSIGN(MousePressedHandler);
477 } // namespace
479 // AutofillDialogViews::AccountChooser -----------------------------------------
481 AutofillDialogViews::AccountChooser::AccountChooser(
482 AutofillDialogViewDelegate* delegate)
483 : image_(new views::ImageView()),
484 menu_button_(new views::MenuButton(NULL, base::string16(), this, true)),
485 link_(new views::Link()),
486 delegate_(delegate) {
487 SetBorder(views::Border::CreateEmptyBorder(0, 0, 0, 10));
488 SetLayoutManager(
489 new views::BoxLayout(views::BoxLayout::kHorizontal, 0, 0,
490 kAroundTextPadding));
491 AddChildView(image_);
493 menu_button_->set_background(NULL);
494 menu_button_->SetBorder(views::Border::NullBorder());
495 gfx::Insets insets = GetInsets();
496 menu_button_->SetFocusPainter(
497 views::Painter::CreateDashedFocusPainterWithInsets(insets));
498 menu_button_->SetFocusable(true);
499 AddChildView(menu_button_);
501 link_->set_listener(this);
502 AddChildView(link_);
505 AutofillDialogViews::AccountChooser::~AccountChooser() {}
507 void AutofillDialogViews::AccountChooser::Update() {
508 SetVisible(delegate_->ShouldShowAccountChooser());
510 gfx::Image icon = delegate_->AccountChooserImage();
511 image_->SetImage(icon.AsImageSkia());
512 menu_button_->SetText(delegate_->AccountChooserText());
513 // This allows the button to shrink if the new text is smaller.
514 menu_button_->ClearMaxTextSize();
516 bool show_link = !delegate_->MenuModelForAccountChooser();
517 menu_button_->SetVisible(!show_link);
518 link_->SetText(delegate_->SignInLinkText());
519 link_->SetVisible(show_link);
521 menu_runner_.reset();
523 PreferredSizeChanged();
526 void AutofillDialogViews::AccountChooser::OnMenuButtonClicked(
527 views::View* source,
528 const gfx::Point& point) {
529 DCHECK_EQ(menu_button_, source);
531 ui::MenuModel* model = delegate_->MenuModelForAccountChooser();
532 if (!model)
533 return;
535 menu_runner_.reset(new views::MenuRunner(model));
536 if (menu_runner_->RunMenuAt(source->GetWidget(),
537 NULL,
538 source->GetBoundsInScreen(),
539 views::MENU_ANCHOR_TOPRIGHT,
540 ui::MENU_SOURCE_NONE,
541 0) == views::MenuRunner::MENU_DELETED) {
542 return;
546 views::View* AutofillDialogViews::GetLoadingShieldForTesting() {
547 return loading_shield_;
550 views::WebView* AutofillDialogViews::GetSignInWebViewForTesting() {
551 return sign_in_web_view_;
554 views::View* AutofillDialogViews::GetNotificationAreaForTesting() {
555 return notification_area_;
558 views::View* AutofillDialogViews::GetScrollableAreaForTesting() {
559 return scrollable_area_;
562 void AutofillDialogViews::AccountChooser::LinkClicked(views::Link* source,
563 int event_flags) {
564 delegate_->SignInLinkClicked();
567 // AutofillDialogViews::OverlayView --------------------------------------------
569 AutofillDialogViews::OverlayView::OverlayView(
570 AutofillDialogViewDelegate* delegate)
571 : delegate_(delegate),
572 image_view_(new views::ImageView()),
573 message_view_(new views::Label()) {
574 message_view_->SetAutoColorReadabilityEnabled(false);
575 message_view_->SetMultiLine(true);
577 AddChildView(image_view_);
578 AddChildView(message_view_);
581 AutofillDialogViews::OverlayView::~OverlayView() {}
583 int AutofillDialogViews::OverlayView::GetHeightForContentsForWidth(int width) {
584 // In this case, 0 means "no preference".
585 if (!message_view_->visible())
586 return 0;
588 return kOverlayImageBottomMargin +
589 views::kButtonVEdgeMarginNew +
590 message_view_->GetHeightForWidth(width) +
591 image_view_->GetHeightForWidth(width);
594 void AutofillDialogViews::OverlayView::UpdateState() {
595 const DialogOverlayState& state = delegate_->GetDialogOverlay();
597 if (state.image.IsEmpty()) {
598 SetVisible(false);
599 return;
602 image_view_->SetImage(state.image.ToImageSkia());
604 message_view_->SetVisible(!state.string.text.empty());
605 message_view_->SetText(state.string.text);
606 message_view_->SetFontList(state.string.font_list);
607 message_view_->SetEnabledColor(GetNativeTheme()->GetSystemColor(
608 ui::NativeTheme::kColorId_TextfieldReadOnlyColor));
610 message_view_->SetBorder(
611 views::Border::CreateEmptyBorder(kOverlayMessageVerticalPadding,
612 kDialogEdgePadding,
613 kOverlayMessageVerticalPadding,
614 kDialogEdgePadding));
616 SetVisible(true);
619 gfx::Insets AutofillDialogViews::OverlayView::GetInsets() const {
620 return gfx::Insets(12, 12, 12, 12);
623 void AutofillDialogViews::OverlayView::Layout() {
624 gfx::Rect bounds = ContentBoundsSansBubbleBorder();
625 if (!message_view_->visible()) {
626 image_view_->SetBoundsRect(bounds);
627 return;
630 int message_height = message_view_->GetHeightForWidth(bounds.width());
631 int y = bounds.bottom() - message_height;
632 message_view_->SetBounds(bounds.x(), y, bounds.width(), message_height);
634 gfx::Size image_size = image_view_->GetPreferredSize();
635 y -= image_size.height() + kOverlayImageBottomMargin;
636 image_view_->SetBounds(bounds.x(), y, bounds.width(), image_size.height());
639 const char* AutofillDialogViews::OverlayView::GetClassName() const {
640 return kOverlayViewClassName;
643 void AutofillDialogViews::OverlayView::OnPaint(gfx::Canvas* canvas) {
644 // BubbleFrameView doesn't mask the window, it just draws the border via
645 // image assets. Match that rounding here.
646 gfx::Rect rect = ContentBoundsSansBubbleBorder();
647 const SkScalar kCornerRadius = SkIntToScalar(
648 GetBubbleBorder() ? GetBubbleBorder()->GetBorderCornerRadius() : 2);
649 gfx::Path window_mask;
650 window_mask.addRoundRect(gfx::RectToSkRect(rect),
651 kCornerRadius, kCornerRadius);
652 canvas->ClipPath(window_mask);
654 OnPaintBackground(canvas);
656 // Draw the arrow, border, and fill for the bottom area.
657 if (message_view_->visible()) {
658 const int arrow_half_width = kArrowWidth / 2.0f;
659 SkPath arrow;
660 int y = message_view_->y() - 1;
661 // Note that we purposely draw slightly outside of |rect| so that the
662 // stroke is hidden on the sides.
663 arrow.moveTo(rect.x() - 1, y);
664 arrow.rLineTo(rect.width() / 2 - arrow_half_width, 0);
665 arrow.rLineTo(arrow_half_width, -kArrowHeight);
666 arrow.rLineTo(arrow_half_width, kArrowHeight);
667 arrow.lineTo(rect.right() + 1, y);
668 arrow.lineTo(rect.right() + 1, rect.bottom() + 1);
669 arrow.lineTo(rect.x() - 1, rect.bottom() + 1);
670 arrow.close();
672 // The mocked alpha blends were 7 for background & 10 for the border against
673 // a very bright background. The eye perceives luminance differences of
674 // darker colors much less than lighter colors, so increase the alpha blend
675 // amount the darker the color (lower the luminance).
676 SkPaint paint;
677 SkColor background_color = background()->get_color();
678 int background_luminance =
679 color_utils::GetLuminanceForColor(background_color);
680 int background_alpha = static_cast<int>(
681 7 + 15 * (255 - background_luminance) / 255);
682 int subtle_border_alpha = static_cast<int>(
683 10 + 20 * (255 - background_luminance) / 255);
685 paint.setColor(color_utils::BlendTowardOppositeLuminance(
686 background_color, background_alpha));
687 paint.setStyle(SkPaint::kFill_Style);
688 canvas->DrawPath(arrow, paint);
689 paint.setColor(color_utils::BlendTowardOppositeLuminance(
690 background_color, subtle_border_alpha));
691 paint.setStyle(SkPaint::kStroke_Style);
692 canvas->DrawPath(arrow, paint);
695 PaintChildren(canvas, views::CullSet());
698 void AutofillDialogViews::OverlayView::OnNativeThemeChanged(
699 const ui::NativeTheme* theme) {
700 set_background(views::Background::CreateSolidBackground(
701 theme->GetSystemColor(ui::NativeTheme::kColorId_DialogBackground)));
704 views::BubbleBorder* AutofillDialogViews::OverlayView::GetBubbleBorder() {
705 views::View* frame = GetWidget()->non_client_view()->frame_view();
706 std::string bubble_frame_view_name(views::BubbleFrameView::kViewClassName);
707 if (frame->GetClassName() == bubble_frame_view_name)
708 return static_cast<views::BubbleFrameView*>(frame)->bubble_border();
709 NOTREACHED();
710 return NULL;
713 gfx::Rect AutofillDialogViews::OverlayView::ContentBoundsSansBubbleBorder() {
714 gfx::Rect bounds = GetContentsBounds();
715 int bubble_width = 5;
716 if (GetBubbleBorder())
717 bubble_width = GetBubbleBorder()->GetBorderThickness();
718 bounds.Inset(bubble_width, bubble_width, bubble_width, bubble_width);
719 return bounds;
722 // AutofillDialogViews::NotificationArea ---------------------------------------
724 AutofillDialogViews::NotificationArea::NotificationArea(
725 AutofillDialogViewDelegate* delegate)
726 : delegate_(delegate) {
727 // Reserve vertical space for the arrow (regardless of whether one exists).
728 // The -1 accounts for the border.
729 SetBorder(views::Border::CreateEmptyBorder(kArrowHeight - 1, 0, 0, 0));
731 views::BoxLayout* box_layout =
732 new views::BoxLayout(views::BoxLayout::kVertical, 0, 0, 0);
733 SetLayoutManager(box_layout);
736 AutofillDialogViews::NotificationArea::~NotificationArea() {}
738 void AutofillDialogViews::NotificationArea::SetNotifications(
739 const std::vector<DialogNotification>& notifications) {
740 notifications_ = notifications;
742 RemoveAllChildViews(true);
744 if (notifications_.empty())
745 return;
747 for (size_t i = 0; i < notifications_.size(); ++i) {
748 const DialogNotification& notification = notifications_[i];
749 scoped_ptr<NotificationView> view(new NotificationView(notification,
750 delegate_));
752 AddChildView(view.release());
755 PreferredSizeChanged();
758 gfx::Size AutofillDialogViews::NotificationArea::GetPreferredSize() const {
759 gfx::Size size = views::View::GetPreferredSize();
760 // Ensure that long notifications wrap and don't enlarge the dialog.
761 size.set_width(1);
762 return size;
765 const char* AutofillDialogViews::NotificationArea::GetClassName() const {
766 return kNotificationAreaClassName;
769 void AutofillDialogViews::NotificationArea::PaintChildren(
770 gfx::Canvas* canvas,
771 const views::CullSet& cull_set) {
774 void AutofillDialogViews::NotificationArea::OnPaint(gfx::Canvas* canvas) {
775 views::View::OnPaint(canvas);
776 views::View::PaintChildren(canvas, views::CullSet());
778 if (HasArrow()) {
779 DrawArrow(
780 canvas,
781 GetMirroredXInView(width() - arrow_centering_anchor_->width() / 2.0f),
782 notifications_[0].GetBackgroundColor(),
783 notifications_[0].GetBorderColor());
787 void AutofillDialogViews::OnWidgetDestroying(views::Widget* widget) {
788 if (widget == window_)
789 window_->GetRootView()->RemovePostTargetHandler(event_handler_.get());
792 void AutofillDialogViews::OnWidgetClosing(views::Widget* widget) {
793 observer_.Remove(widget);
794 if (error_bubble_ && error_bubble_->GetWidget() == widget)
795 error_bubble_ = NULL;
798 void AutofillDialogViews::OnWidgetBoundsChanged(views::Widget* widget,
799 const gfx::Rect& new_bounds) {
800 if (error_bubble_ && error_bubble_->GetWidget() == widget)
801 return;
803 // Notify the web contents of its new auto-resize limits.
804 if (sign_in_delegate_ && sign_in_web_view_->visible()) {
805 sign_in_delegate_->UpdateLimitsAndEnableAutoResize(
806 GetMinimumSignInViewSize(), GetMaximumSignInViewSize());
808 HideErrorBubble();
811 bool AutofillDialogViews::NotificationArea::HasArrow() {
812 return !notifications_.empty() && notifications_[0].HasArrow() &&
813 arrow_centering_anchor_.get();
816 // AutofillDialogViews::SectionContainer ---------------------------------------
818 AutofillDialogViews::SectionContainer::SectionContainer(
819 const base::string16& label,
820 views::View* controls,
821 views::Button* proxy_button)
822 : proxy_button_(proxy_button),
823 forward_mouse_events_(false) {
824 set_notify_enter_exit_on_child(true);
826 SetBorder(views::Border::CreateEmptyBorder(kDetailSectionVerticalPadding,
827 kDialogEdgePadding,
828 kDetailSectionVerticalPadding,
829 kDialogEdgePadding));
831 ui::ResourceBundle& rb = ui::ResourceBundle::GetSharedInstance();
832 views::Label* label_view = new views::Label(
833 label, rb.GetFontList(ui::ResourceBundle::BoldFont));
834 label_view->SetHorizontalAlignment(gfx::ALIGN_LEFT);
836 views::View* label_bar = new views::View();
837 views::GridLayout* label_bar_layout = new views::GridLayout(label_bar);
838 label_bar->SetLayoutManager(label_bar_layout);
839 const int kColumnSetId = 0;
840 views::ColumnSet* columns = label_bar_layout->AddColumnSet(kColumnSetId);
841 columns->AddColumn(
842 views::GridLayout::LEADING,
843 views::GridLayout::LEADING,
845 views::GridLayout::FIXED,
846 kSectionContainerWidth - proxy_button->GetPreferredSize().width(),
848 columns->AddColumn(views::GridLayout::LEADING,
849 views::GridLayout::LEADING,
851 views::GridLayout::USE_PREF,
854 label_bar_layout->StartRow(0, kColumnSetId);
855 label_bar_layout->AddView(label_view);
856 label_bar_layout->AddView(proxy_button);
858 SetLayoutManager(new views::BoxLayout(views::BoxLayout::kVertical, 0, 0, 0));
859 AddChildView(label_bar);
860 AddChildView(controls);
863 AutofillDialogViews::SectionContainer::~SectionContainer() {}
865 void AutofillDialogViews::SectionContainer::SetActive(bool active) {
866 bool is_active = active && proxy_button_->visible();
867 if (is_active == !!background())
868 return;
870 set_background(is_active ?
871 views::Background::CreateSolidBackground(kShadingColor) :
872 NULL);
873 SchedulePaint();
876 void AutofillDialogViews::SectionContainer::SetForwardMouseEvents(
877 bool forward) {
878 forward_mouse_events_ = forward;
879 if (!forward)
880 set_background(NULL);
883 const char* AutofillDialogViews::SectionContainer::GetClassName() const {
884 return kSectionContainerClassName;
887 void AutofillDialogViews::SectionContainer::OnMouseMoved(
888 const ui::MouseEvent& event) {
889 SetActive(ShouldForwardEvent(event));
892 void AutofillDialogViews::SectionContainer::OnMouseEntered(
893 const ui::MouseEvent& event) {
894 if (!ShouldForwardEvent(event))
895 return;
897 SetActive(true);
898 proxy_button_->OnMouseEntered(ProxyEvent(event));
899 SchedulePaint();
902 void AutofillDialogViews::SectionContainer::OnMouseExited(
903 const ui::MouseEvent& event) {
904 SetActive(false);
905 if (!ShouldForwardEvent(event))
906 return;
908 proxy_button_->OnMouseExited(ProxyEvent(event));
909 SchedulePaint();
912 bool AutofillDialogViews::SectionContainer::OnMousePressed(
913 const ui::MouseEvent& event) {
914 if (!ShouldForwardEvent(event))
915 return false;
917 return proxy_button_->OnMousePressed(ProxyEvent(event));
920 void AutofillDialogViews::SectionContainer::OnMouseReleased(
921 const ui::MouseEvent& event) {
922 if (!ShouldForwardEvent(event))
923 return;
925 proxy_button_->OnMouseReleased(ProxyEvent(event));
928 void AutofillDialogViews::SectionContainer::OnGestureEvent(
929 ui::GestureEvent* event) {
930 if (!ShouldForwardEvent(*event))
931 return;
933 proxy_button_->OnGestureEvent(event);
936 views::View* AutofillDialogViews::SectionContainer::GetEventHandlerForRect(
937 const gfx::Rect& rect) {
938 // TODO(tdanderson): Modify this function to support rect-based event
939 // targeting.
941 views::View* handler = views::View::GetEventHandlerForRect(rect);
942 // If the event is not in the label bar and there's no background to be
943 // cleared, let normal event handling take place.
944 if (!background() &&
945 rect.CenterPoint().y() > child_at(0)->bounds().bottom()) {
946 return handler;
949 // Special case for (CVC) inputs in the suggestion view.
950 if (forward_mouse_events_ &&
951 handler->GetAncestorWithClassName(ExpandingTextfield::kViewClassName)) {
952 return handler;
955 // Special case for the proxy button itself.
956 if (handler == proxy_button_)
957 return handler;
959 return this;
962 // static
963 ui::MouseEvent AutofillDialogViews::SectionContainer::ProxyEvent(
964 const ui::MouseEvent& event) {
965 ui::MouseEvent event_copy = event;
966 event_copy.set_location(gfx::Point());
967 return event_copy;
970 bool AutofillDialogViews::SectionContainer::ShouldForwardEvent(
971 const ui::LocatedEvent& event) {
972 // Always forward events on the label bar.
973 return forward_mouse_events_ || event.y() <= child_at(0)->bounds().bottom();
976 // AutofillDialogViews::SuggestedButton ----------------------------------------
978 AutofillDialogViews::SuggestedButton::SuggestedButton(
979 views::MenuButtonListener* listener)
980 : views::MenuButton(NULL, base::string16(), listener, false) {
981 const int kFocusBorderWidth = 1;
982 SetBorder(views::Border::CreateEmptyBorder(kMenuButtonTopInset,
983 kFocusBorderWidth,
984 kMenuButtonBottomInset,
985 kFocusBorderWidth));
986 gfx::Insets insets = GetInsets();
987 insets += gfx::Insets(-kFocusBorderWidth, -kFocusBorderWidth,
988 -kFocusBorderWidth, -kFocusBorderWidth);
989 SetFocusPainter(
990 views::Painter::CreateDashedFocusPainterWithInsets(insets));
991 SetFocusable(true);
994 AutofillDialogViews::SuggestedButton::~SuggestedButton() {}
996 gfx::Size AutofillDialogViews::SuggestedButton::GetPreferredSize() const {
997 ui::ResourceBundle& rb = ui::ResourceBundle::GetSharedInstance();
998 gfx::Size size = rb.GetImageNamed(ResourceIDForState()).Size();
999 const gfx::Insets insets = GetInsets();
1000 size.Enlarge(insets.width(), insets.height());
1001 return size;
1004 const char* AutofillDialogViews::SuggestedButton::GetClassName() const {
1005 return kSuggestedButtonClassName;
1008 void AutofillDialogViews::SuggestedButton::PaintChildren(
1009 gfx::Canvas* canvas,
1010 const views::CullSet& cull_set) {
1013 void AutofillDialogViews::SuggestedButton::OnPaint(gfx::Canvas* canvas) {
1014 ui::ResourceBundle& rb = ui::ResourceBundle::GetSharedInstance();
1015 const gfx::Insets insets = GetInsets();
1016 canvas->DrawImageInt(*rb.GetImageSkiaNamed(ResourceIDForState()),
1017 insets.left(), insets.top());
1018 views::Painter::PaintFocusPainter(this, canvas, focus_painter());
1021 int AutofillDialogViews::SuggestedButton::ResourceIDForState() const {
1022 views::Button::ButtonState button_state = state();
1023 if (button_state == views::Button::STATE_PRESSED)
1024 return IDR_AUTOFILL_DIALOG_MENU_BUTTON_P;
1025 else if (button_state == views::Button::STATE_HOVERED)
1026 return IDR_AUTOFILL_DIALOG_MENU_BUTTON_H;
1027 else if (button_state == views::Button::STATE_DISABLED)
1028 return IDR_AUTOFILL_DIALOG_MENU_BUTTON_D;
1029 DCHECK_EQ(views::Button::STATE_NORMAL, button_state);
1030 return IDR_AUTOFILL_DIALOG_MENU_BUTTON;
1033 // AutofillDialogViews::DetailsContainerView -----------------------------------
1035 AutofillDialogViews::DetailsContainerView::DetailsContainerView(
1036 const base::Closure& callback)
1037 : bounds_changed_callback_(callback),
1038 ignore_layouts_(false) {}
1040 AutofillDialogViews::DetailsContainerView::~DetailsContainerView() {}
1042 void AutofillDialogViews::DetailsContainerView::OnBoundsChanged(
1043 const gfx::Rect& previous_bounds) {
1044 bounds_changed_callback_.Run();
1047 void AutofillDialogViews::DetailsContainerView::Layout() {
1048 if (!ignore_layouts_)
1049 views::View::Layout();
1052 // AutofillDialogViews::SuggestionView -----------------------------------------
1054 AutofillDialogViews::SuggestionView::SuggestionView(
1055 AutofillDialogViews* autofill_dialog)
1056 : label_(new views::Label()),
1057 label_line_2_(new views::Label()),
1058 icon_(new views::ImageView()),
1059 textfield_(
1060 new ExpandingTextfield(base::string16(),
1061 base::string16(),
1062 false,
1063 autofill_dialog)) {
1064 // TODO(estade): Make this the correct color.
1065 SetBorder(views::Border::CreateSolidSidedBorder(1, 0, 0, 0, SK_ColorLTGRAY));
1067 SectionRowView* label_container = new SectionRowView();
1068 AddChildView(label_container);
1070 // Label and icon.
1071 label_container->AddChildView(icon_);
1072 label_->SetHorizontalAlignment(gfx::ALIGN_LEFT);
1073 label_container->AddChildView(label_);
1075 // TODO(estade): get the sizing and spacing right on this textfield.
1076 textfield_->SetVisible(false);
1077 textfield_->SetDefaultWidthInCharacters(15);
1078 label_container->AddChildView(textfield_);
1080 label_line_2_->SetHorizontalAlignment(gfx::ALIGN_LEFT);
1081 label_line_2_->SetVisible(false);
1082 label_line_2_->SetLineHeight(22);
1083 label_line_2_->SetMultiLine(true);
1084 AddChildView(label_line_2_);
1086 SetLayoutManager(new views::BoxLayout(views::BoxLayout::kVertical, 0, 0, 7));
1089 AutofillDialogViews::SuggestionView::~SuggestionView() {}
1091 gfx::Size AutofillDialogViews::SuggestionView::GetPreferredSize() const {
1092 // There's no preferred width. The parent's layout should get the preferred
1093 // height from GetHeightForWidth().
1094 return gfx::Size();
1097 int AutofillDialogViews::SuggestionView::GetHeightForWidth(int width) const {
1098 int height = 0;
1099 CanUseVerticallyCompactText(width, &height);
1100 return height;
1103 bool AutofillDialogViews::SuggestionView::CanUseVerticallyCompactText(
1104 int available_width,
1105 int* resulting_height) const {
1106 // This calculation may be costly, avoid doing it more than once per width.
1107 if (!calculated_heights_.count(available_width)) {
1108 // Changing the state of |this| now will lead to extra layouts and
1109 // paints we don't want, so create another SuggestionView to calculate
1110 // which label we have room to show.
1111 SuggestionView sizing_view(NULL);
1112 sizing_view.SetLabelText(state_.vertically_compact_text);
1113 sizing_view.SetIcon(state_.icon);
1114 sizing_view.SetTextfield(state_.extra_text, state_.extra_icon);
1115 sizing_view.label_->SetSize(gfx::Size(available_width, 0));
1116 sizing_view.label_line_2_->SetSize(gfx::Size(available_width, 0));
1118 // Shortcut |sizing_view|'s GetHeightForWidth() to avoid an infinite loop.
1119 // Its BoxLayout must do these calculations for us.
1120 views::LayoutManager* layout = sizing_view.GetLayoutManager();
1121 if (layout->GetPreferredSize(&sizing_view).width() <= available_width) {
1122 calculated_heights_[available_width] = std::make_pair(
1123 true,
1124 layout->GetPreferredHeightForWidth(&sizing_view, available_width));
1125 } else {
1126 sizing_view.SetLabelText(state_.horizontally_compact_text);
1127 calculated_heights_[available_width] = std::make_pair(
1128 false,
1129 layout->GetPreferredHeightForWidth(&sizing_view, available_width));
1133 const std::pair<bool, int>& values = calculated_heights_[available_width];
1134 *resulting_height = values.second;
1135 return values.first;
1138 void AutofillDialogViews::SuggestionView::OnBoundsChanged(
1139 const gfx::Rect& previous_bounds) {
1140 UpdateLabelText();
1143 void AutofillDialogViews::SuggestionView::SetState(
1144 const SuggestionState& state) {
1145 calculated_heights_.clear();
1146 state_ = state;
1147 SetVisible(state_.visible);
1148 UpdateLabelText();
1149 SetIcon(state_.icon);
1150 SetTextfield(state_.extra_text, state_.extra_icon);
1151 PreferredSizeChanged();
1154 void AutofillDialogViews::SuggestionView::SetLabelText(
1155 const base::string16& text) {
1156 // TODO(estade): does this localize well?
1157 base::string16 line_return(base::ASCIIToUTF16("\n"));
1158 size_t position = text.find(line_return);
1159 if (position == base::string16::npos) {
1160 label_->SetText(text);
1161 label_line_2_->SetVisible(false);
1162 } else {
1163 label_->SetText(text.substr(0, position));
1164 label_line_2_->SetText(text.substr(position + line_return.length()));
1165 label_line_2_->SetVisible(true);
1169 void AutofillDialogViews::SuggestionView::SetIcon(
1170 const gfx::Image& image) {
1171 icon_->SetVisible(!image.IsEmpty());
1172 icon_->SetImage(image.AsImageSkia());
1175 void AutofillDialogViews::SuggestionView::SetTextfield(
1176 const base::string16& placeholder_text,
1177 const gfx::Image& icon) {
1178 textfield_->SetPlaceholderText(placeholder_text);
1179 textfield_->SetIcon(icon);
1180 textfield_->SetVisible(!placeholder_text.empty());
1183 void AutofillDialogViews::SuggestionView::UpdateLabelText() {
1184 int unused;
1185 SetLabelText(CanUseVerticallyCompactText(width(), &unused) ?
1186 state_.vertically_compact_text :
1187 state_.horizontally_compact_text);
1190 // AutofillDialogView ----------------------------------------------------------
1192 // static
1193 AutofillDialogView* AutofillDialogView::Create(
1194 AutofillDialogViewDelegate* delegate) {
1195 return new AutofillDialogViews(delegate);
1198 // AutofillDialogViews ---------------------------------------------------------
1200 AutofillDialogViews::AutofillDialogViews(AutofillDialogViewDelegate* delegate)
1201 : delegate_(delegate),
1202 updates_scope_(0),
1203 needs_update_(false),
1204 window_(NULL),
1205 notification_area_(NULL),
1206 account_chooser_(NULL),
1207 sign_in_web_view_(NULL),
1208 scrollable_area_(NULL),
1209 details_container_(NULL),
1210 loading_shield_(NULL),
1211 loading_shield_height_(0),
1212 overlay_view_(NULL),
1213 button_strip_extra_view_(NULL),
1214 save_in_chrome_checkbox_(NULL),
1215 save_in_chrome_checkbox_container_(NULL),
1216 button_strip_image_(NULL),
1217 footnote_view_(NULL),
1218 legal_document_view_(NULL),
1219 focus_manager_(NULL),
1220 error_bubble_(NULL),
1221 observer_(this) {
1222 DCHECK(delegate);
1223 detail_groups_.insert(std::make_pair(SECTION_CC,
1224 DetailsGroup(SECTION_CC)));
1225 detail_groups_.insert(std::make_pair(SECTION_BILLING,
1226 DetailsGroup(SECTION_BILLING)));
1227 detail_groups_.insert(std::make_pair(SECTION_CC_BILLING,
1228 DetailsGroup(SECTION_CC_BILLING)));
1229 detail_groups_.insert(std::make_pair(SECTION_SHIPPING,
1230 DetailsGroup(SECTION_SHIPPING)));
1233 AutofillDialogViews::~AutofillDialogViews() {
1234 HideErrorBubble();
1235 DCHECK(!window_);
1238 void AutofillDialogViews::Show() {
1239 InitChildViews();
1240 UpdateAccountChooser();
1241 UpdateNotificationArea();
1242 UpdateButtonStripExtraView();
1244 // Ownership of |contents_| is handed off by this call. The widget will take
1245 // care of deleting itself after calling DeleteDelegate().
1246 WebContentsModalDialogManager* web_contents_modal_dialog_manager =
1247 WebContentsModalDialogManager::FromWebContents(
1248 delegate_->GetWebContents());
1249 WebContentsModalDialogManagerDelegate* modal_delegate =
1250 web_contents_modal_dialog_manager->delegate();
1251 DCHECK(modal_delegate);
1252 window_ = views::Widget::CreateWindowAsFramelessChild(
1253 this, modal_delegate->GetWebContentsModalDialogHost()->GetHostView());
1254 web_contents_modal_dialog_manager->ShowModalDialog(
1255 window_->GetNativeView());
1256 focus_manager_ = window_->GetFocusManager();
1257 focus_manager_->AddFocusChangeListener(this);
1259 ShowDialogInMode(DETAIL_INPUT);
1261 // Listen for size changes on the browser.
1262 views::Widget* browser_widget =
1263 views::Widget::GetTopLevelWidgetForNativeView(
1264 delegate_->GetWebContents()->GetNativeView());
1265 observer_.Add(browser_widget);
1267 // Listen for unhandled mouse presses on the non-client view.
1268 event_handler_.reset(new MousePressedHandler(delegate_));
1269 window_->GetRootView()->AddPostTargetHandler(event_handler_.get());
1270 observer_.Add(window_);
1273 void AutofillDialogViews::Hide() {
1274 if (window_)
1275 window_->Close();
1278 void AutofillDialogViews::UpdatesStarted() {
1279 updates_scope_++;
1282 void AutofillDialogViews::UpdatesFinished() {
1283 updates_scope_--;
1284 DCHECK_GE(updates_scope_, 0);
1285 if (updates_scope_ == 0 && needs_update_) {
1286 needs_update_ = false;
1287 ContentsPreferredSizeChanged();
1291 void AutofillDialogViews::UpdateAccountChooser() {
1292 account_chooser_->Update();
1294 bool show_loading = delegate_->ShouldShowSpinner();
1295 if (show_loading != loading_shield_->visible()) {
1296 if (show_loading) {
1297 loading_shield_height_ = std::max(kInitialLoadingShieldHeight,
1298 GetContentsBounds().height());
1299 ShowDialogInMode(LOADING);
1300 } else {
1301 bool show_sign_in = delegate_->ShouldShowSignInWebView();
1302 ShowDialogInMode(show_sign_in ? SIGN_IN : DETAIL_INPUT);
1305 InvalidateLayout();
1306 ContentsPreferredSizeChanged();
1309 // Update legal documents for the account.
1310 if (footnote_view_) {
1311 const base::string16 text = delegate_->LegalDocumentsText();
1312 legal_document_view_->SetText(text);
1314 if (!text.empty()) {
1315 const std::vector<gfx::Range>& link_ranges =
1316 delegate_->LegalDocumentLinks();
1317 for (size_t i = 0; i < link_ranges.size(); ++i) {
1318 views::StyledLabel::RangeStyleInfo link_range_info =
1319 views::StyledLabel::RangeStyleInfo::CreateForLink();
1320 link_range_info.disable_line_wrapping = false;
1321 legal_document_view_->AddStyleRange(link_ranges[i], link_range_info);
1325 footnote_view_->SetVisible(!text.empty());
1326 ContentsPreferredSizeChanged();
1329 if (GetWidget())
1330 GetWidget()->UpdateWindowTitle();
1333 void AutofillDialogViews::UpdateButtonStrip() {
1334 button_strip_extra_view_->SetVisible(
1335 GetDialogButtons() != ui::DIALOG_BUTTON_NONE);
1336 UpdateButtonStripExtraView();
1337 GetDialogClientView()->UpdateDialogButtons();
1339 ContentsPreferredSizeChanged();
1342 void AutofillDialogViews::UpdateOverlay() {
1343 overlay_view_->UpdateState();
1344 ContentsPreferredSizeChanged();
1347 void AutofillDialogViews::UpdateDetailArea() {
1348 scrollable_area_->SetVisible(true);
1349 ContentsPreferredSizeChanged();
1352 void AutofillDialogViews::UpdateForErrors() {
1353 ValidateForm();
1356 void AutofillDialogViews::UpdateNotificationArea() {
1357 DCHECK(notification_area_);
1358 notification_area_->SetNotifications(delegate_->CurrentNotifications());
1359 ContentsPreferredSizeChanged();
1362 void AutofillDialogViews::UpdateSection(DialogSection section) {
1363 UpdateSectionImpl(section, true);
1366 void AutofillDialogViews::UpdateErrorBubble() {
1367 if (!delegate_->ShouldShowErrorBubble())
1368 HideErrorBubble();
1371 void AutofillDialogViews::FillSection(DialogSection section,
1372 ServerFieldType originating_type) {
1373 DetailsGroup* group = GroupForSection(section);
1374 // Make sure to overwrite the originating input if it exists.
1375 TextfieldMap::iterator text_mapping =
1376 group->textfields.find(originating_type);
1377 if (text_mapping != group->textfields.end())
1378 text_mapping->second->SetText(base::string16());
1380 // If the Autofill data comes from a credit card, make sure to overwrite the
1381 // CC comboboxes (even if they already have something in them). If the
1382 // Autofill data comes from an AutofillProfile, leave the comboboxes alone.
1383 if (section == GetCreditCardSection() &&
1384 AutofillType(originating_type).group() == CREDIT_CARD) {
1385 for (ComboboxMap::const_iterator it = group->comboboxes.begin();
1386 it != group->comboboxes.end(); ++it) {
1387 if (AutofillType(it->first).group() == CREDIT_CARD)
1388 it->second->SetSelectedIndex(it->second->model()->GetDefaultIndex());
1392 UpdateSectionImpl(section, false);
1395 void AutofillDialogViews::GetUserInput(DialogSection section,
1396 FieldValueMap* output) {
1397 DetailsGroup* group = GroupForSection(section);
1398 for (TextfieldMap::const_iterator it = group->textfields.begin();
1399 it != group->textfields.end(); ++it) {
1400 output->insert(std::make_pair(it->first, it->second->GetText()));
1402 for (ComboboxMap::const_iterator it = group->comboboxes.begin();
1403 it != group->comboboxes.end(); ++it) {
1404 output->insert(std::make_pair(it->first,
1405 it->second->model()->GetItemAt(it->second->selected_index())));
1409 base::string16 AutofillDialogViews::GetCvc() {
1410 return GroupForSection(GetCreditCardSection())->suggested_info->
1411 textfield()->GetText();
1414 bool AutofillDialogViews::SaveDetailsLocally() {
1415 DCHECK(save_in_chrome_checkbox_->visible());
1416 return save_in_chrome_checkbox_->checked();
1419 const content::NavigationController* AutofillDialogViews::ShowSignIn() {
1420 // The initial minimum width and height are set such that the dialog
1421 // won't change size before the page is loaded.
1422 int min_width = GetContentsBounds().width();
1423 // The height has to include the button strip.
1424 int min_height = GetDialogClientView()->GetContentsBounds().height();
1426 // TODO(abodenha): We should be able to use the WebContents of the WebView
1427 // to navigate instead of LoadInitialURL. Figure out why it doesn't work.
1428 sign_in_delegate_.reset(
1429 new AutofillDialogSignInDelegate(
1430 this,
1431 sign_in_web_view_->GetWebContents(),
1432 delegate_->GetWebContents(),
1433 gfx::Size(min_width, min_height), GetMaximumSignInViewSize()));
1434 sign_in_web_view_->LoadInitialURL(delegate_->SignInUrl());
1436 ShowDialogInMode(SIGN_IN);
1438 ContentsPreferredSizeChanged();
1440 return &sign_in_web_view_->web_contents()->GetController();
1443 void AutofillDialogViews::HideSignIn() {
1444 sign_in_web_view_->SetWebContents(NULL);
1446 if (delegate_->ShouldShowSpinner()) {
1447 UpdateAccountChooser();
1448 } else {
1449 ShowDialogInMode(DETAIL_INPUT);
1450 InvalidateLayout();
1452 DCHECK(!sign_in_web_view_->visible());
1454 ContentsPreferredSizeChanged();
1457 void AutofillDialogViews::ModelChanged() {
1458 menu_runner_.reset();
1460 for (DetailGroupMap::const_iterator iter = detail_groups_.begin();
1461 iter != detail_groups_.end(); ++iter) {
1462 UpdateDetailsGroupState(iter->second);
1466 void AutofillDialogViews::OnSignInResize(const gfx::Size& pref_size) {
1467 sign_in_web_view_->SetPreferredSize(pref_size);
1468 ContentsPreferredSizeChanged();
1471 void AutofillDialogViews::ValidateSection(DialogSection section) {
1472 ValidateGroup(*GroupForSection(section), VALIDATE_EDIT);
1475 gfx::Size AutofillDialogViews::GetPreferredSize() const {
1476 if (preferred_size_.IsEmpty())
1477 preferred_size_ = CalculatePreferredSize(false);
1479 return preferred_size_;
1482 gfx::Size AutofillDialogViews::GetMinimumSize() const {
1483 return CalculatePreferredSize(true);
1486 void AutofillDialogViews::Layout() {
1487 const gfx::Rect content_bounds = GetContentsBounds();
1488 if (sign_in_web_view_->visible()) {
1489 sign_in_web_view_->SetBoundsRect(content_bounds);
1490 return;
1493 if (loading_shield_->visible()) {
1494 loading_shield_->SetBoundsRect(bounds());
1495 return;
1498 const int x = content_bounds.x();
1499 const int y = content_bounds.y();
1500 const int width = content_bounds.width();
1501 // Layout notification area at top of dialog.
1502 int notification_height = notification_area_->GetHeightForWidth(width);
1503 notification_area_->SetBounds(x, y, width, notification_height);
1505 // The rest (the |scrollable_area_|) takes up whatever's left.
1506 if (scrollable_area_->visible()) {
1507 int scroll_y = y;
1508 if (notification_height > notification_area_->GetInsets().height())
1509 scroll_y += notification_height + views::kRelatedControlVerticalSpacing;
1511 int scroll_bottom = content_bounds.bottom();
1512 DCHECK_EQ(scrollable_area_->contents(), details_container_);
1513 details_container_->SizeToPreferredSize();
1514 details_container_->Layout();
1515 // TODO(estade): remove this hack. See crbug.com/285996
1516 details_container_->set_ignore_layouts(true);
1517 scrollable_area_->SetBounds(x, scroll_y, width, scroll_bottom - scroll_y);
1518 details_container_->set_ignore_layouts(false);
1521 if (error_bubble_)
1522 error_bubble_->UpdatePosition();
1525 void AutofillDialogViews::OnNativeThemeChanged(
1526 const ui::NativeTheme* theme) {
1527 if (!legal_document_view_)
1528 return;
1530 // NOTE: This color may change because of |auto_color_readability|, set on
1531 // |legal_document_view_|.
1532 views::StyledLabel::RangeStyleInfo default_style;
1533 default_style.color =
1534 theme->GetSystemColor(ui::NativeTheme::kColorId_LabelDisabledColor);
1536 legal_document_view_->SetDefaultStyle(default_style);
1539 base::string16 AutofillDialogViews::GetWindowTitle() const {
1540 base::string16 title = delegate_->DialogTitle();
1541 // Hack alert: we don't want the dialog to jiggle when a title is added or
1542 // removed. Setting a non-empty string here keeps the dialog's title bar the
1543 // same size.
1544 return title.empty() ? base::ASCIIToUTF16(" ") : title;
1547 void AutofillDialogViews::WindowClosing() {
1548 focus_manager_->RemoveFocusChangeListener(this);
1551 void AutofillDialogViews::DeleteDelegate() {
1552 window_ = NULL;
1553 // |this| belongs to the controller (|delegate_|).
1554 delegate_->ViewClosed();
1557 int AutofillDialogViews::GetDialogButtons() const {
1558 return delegate_->GetDialogButtons();
1561 int AutofillDialogViews::GetDefaultDialogButton() const {
1562 if (GetDialogButtons() & ui::DIALOG_BUTTON_OK)
1563 return ui::DIALOG_BUTTON_OK;
1565 return ui::DIALOG_BUTTON_NONE;
1568 base::string16 AutofillDialogViews::GetDialogButtonLabel(
1569 ui::DialogButton button) const {
1570 return button == ui::DIALOG_BUTTON_OK ?
1571 delegate_->ConfirmButtonText() : delegate_->CancelButtonText();
1574 bool AutofillDialogViews::ShouldDefaultButtonBeBlue() const {
1575 return true;
1578 bool AutofillDialogViews::IsDialogButtonEnabled(ui::DialogButton button) const {
1579 return delegate_->IsDialogButtonEnabled(button);
1582 views::View* AutofillDialogViews::GetInitiallyFocusedView() {
1583 if (!window_ || !focus_manager_)
1584 return NULL;
1586 if (sign_in_web_view_->visible())
1587 return sign_in_web_view_;
1589 if (loading_shield_->visible())
1590 return views::DialogDelegateView::GetInitiallyFocusedView();
1592 DCHECK(scrollable_area_->visible());
1594 views::FocusManager* manager = focus_manager_;
1595 for (views::View* next = scrollable_area_;
1596 next;
1597 next = manager->GetNextFocusableView(next, window_, false, true)) {
1598 views::View* input_view = GetAncestralInputView(next);
1599 if (!input_view)
1600 continue;
1602 // If there are no invalid inputs, return the first input found. Otherwise,
1603 // return the first invalid input found.
1604 if (validity_map_.empty() ||
1605 validity_map_.find(input_view) != validity_map_.end()) {
1606 return next;
1610 return views::DialogDelegateView::GetInitiallyFocusedView();
1613 views::View* AutofillDialogViews::CreateExtraView() {
1614 return button_strip_extra_view_;
1617 views::View* AutofillDialogViews::CreateTitlebarExtraView() {
1618 return account_chooser_;
1621 views::View* AutofillDialogViews::CreateFootnoteView() {
1622 footnote_view_ = new LayoutPropagationView();
1623 footnote_view_->SetLayoutManager(
1624 new views::BoxLayout(views::BoxLayout::kVertical,
1625 kDialogEdgePadding,
1626 kDialogEdgePadding,
1627 0));
1628 footnote_view_->SetBorder(
1629 views::Border::CreateSolidSidedBorder(1, 0, 0, 0, kSubtleBorderColor));
1630 footnote_view_->set_background(
1631 views::Background::CreateSolidBackground(kShadingColor));
1633 legal_document_view_ = new views::StyledLabel(base::string16(), this);
1635 footnote_view_->AddChildView(legal_document_view_);
1636 footnote_view_->SetVisible(false);
1638 return footnote_view_;
1641 views::View* AutofillDialogViews::CreateOverlayView() {
1642 return overlay_view_;
1645 bool AutofillDialogViews::Cancel() {
1646 return delegate_->OnCancel();
1649 bool AutofillDialogViews::Accept() {
1650 if (ValidateForm())
1651 return delegate_->OnAccept();
1653 // |ValidateForm()| failed; there should be invalid views in |validity_map_|.
1654 DCHECK(!validity_map_.empty());
1655 FocusInitialView();
1657 return false;
1660 void AutofillDialogViews::ContentsChanged(views::Textfield* sender,
1661 const base::string16& new_contents) {
1662 InputEditedOrActivated(TypeForTextfield(sender),
1663 sender->GetBoundsInScreen(),
1664 true);
1666 const ExpandingTextfield* expanding = static_cast<ExpandingTextfield*>(
1667 sender->GetAncestorWithClassName(ExpandingTextfield::kViewClassName));
1668 if (expanding && expanding->needs_layout())
1669 ContentsPreferredSizeChanged();
1672 bool AutofillDialogViews::HandleKeyEvent(views::Textfield* sender,
1673 const ui::KeyEvent& key_event) {
1674 ui::KeyEvent copy(key_event);
1675 content::NativeWebKeyboardEvent event(&copy);
1676 return delegate_->HandleKeyPressEventInInput(event);
1679 bool AutofillDialogViews::HandleMouseEvent(views::Textfield* sender,
1680 const ui::MouseEvent& mouse_event) {
1681 if (mouse_event.IsLeftMouseButton() && sender->HasFocus()) {
1682 InputEditedOrActivated(TypeForTextfield(sender),
1683 sender->GetBoundsInScreen(),
1684 false);
1685 // Show an error bubble if a user clicks on an input that's already focused
1686 // (and invalid).
1687 ShowErrorBubbleForViewIfNecessary(sender);
1690 return false;
1693 void AutofillDialogViews::OnWillChangeFocus(
1694 views::View* focused_before,
1695 views::View* focused_now) {
1696 delegate_->FocusMoved();
1697 HideErrorBubble();
1700 void AutofillDialogViews::OnDidChangeFocus(
1701 views::View* focused_before,
1702 views::View* focused_now) {
1703 // If user leaves an edit-field, revalidate the group it belongs to.
1704 if (focused_before) {
1705 DetailsGroup* group = GroupForView(focused_before);
1706 if (group && group->container->visible())
1707 ValidateGroup(*group, VALIDATE_EDIT);
1710 // Show an error bubble when the user focuses the input.
1711 if (focused_now) {
1712 focused_now->ScrollRectToVisible(focused_now->GetLocalBounds());
1713 ShowErrorBubbleForViewIfNecessary(focused_now);
1717 void AutofillDialogViews::OnPerformAction(views::Combobox* combobox) {
1718 DialogSection section = GroupForView(combobox)->section;
1719 InputEditedOrActivated(TypeForCombobox(combobox), gfx::Rect(), true);
1720 // NOTE: |combobox| may have been deleted.
1721 ValidateGroup(*GroupForSection(section), VALIDATE_EDIT);
1722 SetEditabilityForSection(section);
1725 void AutofillDialogViews::StyledLabelLinkClicked(const gfx::Range& range,
1726 int event_flags) {
1727 delegate_->LegalDocumentLinkClicked(range);
1730 void AutofillDialogViews::OnMenuButtonClicked(views::View* source,
1731 const gfx::Point& point) {
1732 DCHECK_EQ(kSuggestedButtonClassName, source->GetClassName());
1734 DetailsGroup* group = NULL;
1735 for (DetailGroupMap::iterator iter = detail_groups_.begin();
1736 iter != detail_groups_.end(); ++iter) {
1737 if (source == iter->second.suggested_button) {
1738 group = &iter->second;
1739 break;
1742 DCHECK(group);
1744 if (!group->suggested_button->visible())
1745 return;
1747 menu_runner_.reset(new views::MenuRunner(
1748 delegate_->MenuModelForSection(group->section)));
1750 group->container->SetActive(true);
1751 views::Button::ButtonState state = group->suggested_button->state();
1752 group->suggested_button->SetState(views::Button::STATE_PRESSED);
1754 gfx::Rect screen_bounds = source->GetBoundsInScreen();
1755 screen_bounds.Inset(source->GetInsets());
1756 if (menu_runner_->RunMenuAt(source->GetWidget(),
1757 NULL,
1758 screen_bounds,
1759 views::MENU_ANCHOR_TOPRIGHT,
1760 ui::MENU_SOURCE_NONE,
1761 0) == views::MenuRunner::MENU_DELETED) {
1762 return;
1765 group->container->SetActive(false);
1766 group->suggested_button->SetState(state);
1769 gfx::Size AutofillDialogViews::CalculatePreferredSize(
1770 bool get_minimum_size) const {
1771 gfx::Insets insets = GetInsets();
1772 gfx::Size scroll_size = scrollable_area_->contents()->GetPreferredSize();
1773 // The width is always set by the scroll area.
1774 const int width = scroll_size.width();
1776 if (sign_in_web_view_->visible()) {
1777 const gfx::Size size = static_cast<views::View*>(sign_in_web_view_)->
1778 GetPreferredSize();
1779 return gfx::Size(width + insets.width(), size.height() + insets.height());
1782 if (overlay_view_->visible()) {
1783 const int height = overlay_view_->GetHeightForContentsForWidth(width);
1784 if (height != 0)
1785 return gfx::Size(width + insets.width(), height + insets.height());
1788 if (loading_shield_->visible()) {
1789 return gfx::Size(width + insets.width(),
1790 loading_shield_height_ + insets.height());
1793 int height = 0;
1794 const int notification_height = notification_area_->GetHeightForWidth(width);
1795 if (notification_height > notification_area_->GetInsets().height())
1796 height += notification_height + views::kRelatedControlVerticalSpacing;
1798 if (scrollable_area_->visible())
1799 height += get_minimum_size ? kMinimumContentsHeight : scroll_size.height();
1801 return gfx::Size(width + insets.width(), height + insets.height());
1804 gfx::Size AutofillDialogViews::GetMinimumSignInViewSize() const {
1805 return gfx::Size(GetDialogClientView()->size().width() - GetInsets().width(),
1806 kMinimumContentsHeight);
1809 gfx::Size AutofillDialogViews::GetMaximumSignInViewSize() const {
1810 web_modal::WebContentsModalDialogHost* dialog_host =
1811 WebContentsModalDialogManager::FromWebContents(
1812 delegate_->GetWebContents())->delegate()->
1813 GetWebContentsModalDialogHost();
1815 // Inset the maximum dialog height to get the maximum content height.
1816 int height = dialog_host->GetMaximumDialogSize().height();
1817 const int non_client_height = GetWidget()->non_client_view()->height();
1818 const int client_height = GetWidget()->client_view()->height();
1819 // TODO(msw): Resolve the 12 pixel discrepancy; is that the bubble border?
1820 height -= non_client_height - client_height - 12;
1821 height = std::max(height, kMinimumContentsHeight);
1823 // The dialog's width never changes.
1824 const int width = GetDialogClientView()->size().width() - GetInsets().width();
1825 return gfx::Size(width, height);
1828 DialogSection AutofillDialogViews::GetCreditCardSection() const {
1829 if (delegate_->SectionIsActive(SECTION_CC))
1830 return SECTION_CC;
1832 DCHECK(delegate_->SectionIsActive(SECTION_CC_BILLING));
1833 return SECTION_CC_BILLING;
1836 void AutofillDialogViews::InitChildViews() {
1837 button_strip_extra_view_ = new LayoutPropagationView();
1838 button_strip_extra_view_->SetLayoutManager(
1839 new views::BoxLayout(views::BoxLayout::kHorizontal, 0, 0, 0));
1841 save_in_chrome_checkbox_container_ = new views::View();
1842 save_in_chrome_checkbox_container_->SetLayoutManager(
1843 new views::BoxLayout(views::BoxLayout::kHorizontal, 0, 0, 7));
1844 button_strip_extra_view_->AddChildView(save_in_chrome_checkbox_container_);
1846 save_in_chrome_checkbox_ =
1847 new views::Checkbox(delegate_->SaveLocallyText());
1848 save_in_chrome_checkbox_->SetChecked(delegate_->ShouldSaveInChrome());
1849 save_in_chrome_checkbox_container_->AddChildView(save_in_chrome_checkbox_);
1851 save_in_chrome_checkbox_container_->AddChildView(
1852 new TooltipIcon(delegate_->SaveLocallyTooltip()));
1854 button_strip_image_ = new views::ImageView();
1855 button_strip_extra_view_->AddChildView(button_strip_image_);
1857 account_chooser_ = new AccountChooser(delegate_);
1858 notification_area_ = new NotificationArea(delegate_);
1859 notification_area_->set_arrow_centering_anchor(account_chooser_->AsWeakPtr());
1860 AddChildView(notification_area_);
1862 scrollable_area_ = new views::ScrollView();
1863 scrollable_area_->set_hide_horizontal_scrollbar(true);
1864 scrollable_area_->SetContents(CreateDetailsContainer());
1865 AddChildView(scrollable_area_);
1867 loading_shield_ = new LoadingAnimationView(delegate_->SpinnerText());
1868 AddChildView(loading_shield_);
1870 sign_in_web_view_ = new views::WebView(delegate_->profile());
1871 AddChildView(sign_in_web_view_);
1873 overlay_view_ = new OverlayView(delegate_);
1874 overlay_view_->SetVisible(false);
1877 views::View* AutofillDialogViews::CreateDetailsContainer() {
1878 details_container_ = new DetailsContainerView(
1879 base::Bind(&AutofillDialogViews::DetailsContainerBoundsChanged,
1880 base::Unretained(this)));
1882 // A box layout is used because it respects widget visibility.
1883 details_container_->SetLayoutManager(
1884 new views::BoxLayout(views::BoxLayout::kVertical, 0, 0, 0));
1885 for (DetailGroupMap::iterator iter = detail_groups_.begin();
1886 iter != detail_groups_.end(); ++iter) {
1887 CreateDetailsSection(iter->second.section);
1888 details_container_->AddChildView(iter->second.container);
1891 return details_container_;
1894 void AutofillDialogViews::CreateDetailsSection(DialogSection section) {
1895 // Inputs container (manual inputs + combobox).
1896 views::View* inputs_container = CreateInputsContainer(section);
1898 DetailsGroup* group = GroupForSection(section);
1899 // Container (holds label + inputs).
1900 group->container = new SectionContainer(delegate_->LabelForSection(section),
1901 inputs_container,
1902 group->suggested_button);
1903 DCHECK(group->suggested_button->parent());
1904 UpdateDetailsGroupState(*group);
1907 views::View* AutofillDialogViews::CreateInputsContainer(DialogSection section) {
1908 // The |info_view| holds |manual_inputs| and |suggested_info|, allowing the
1909 // dialog to toggle which is shown.
1910 views::View* info_view = new views::View();
1911 info_view->SetLayoutManager(
1912 new views::BoxLayout(views::BoxLayout::kVertical, 0, 0, 0));
1914 DetailsGroup* group = GroupForSection(section);
1915 group->manual_input = new views::View();
1916 InitInputsView(section);
1917 info_view->AddChildView(group->manual_input);
1919 group->suggested_info = new SuggestionView(this);
1920 info_view->AddChildView(group->suggested_info);
1922 // TODO(estade): It might be slightly more OO if this button were created
1923 // and listened to by the section container.
1924 group->suggested_button = new SuggestedButton(this);
1926 return info_view;
1929 // TODO(estade): we should be using Chrome-style constrained window padding
1930 // values.
1931 void AutofillDialogViews::InitInputsView(DialogSection section) {
1932 DetailsGroup* group = GroupForSection(section);
1933 EraseInvalidViewsInGroup(group);
1935 TextfieldMap* textfields = &group->textfields;
1936 textfields->clear();
1938 ComboboxMap* comboboxes = &group->comboboxes;
1939 comboboxes->clear();
1941 views::View* view = group->manual_input;
1942 view->RemoveAllChildViews(true);
1944 views::GridLayout* layout = new views::GridLayout(view);
1945 view->SetLayoutManager(layout);
1947 int column_set_id = 0;
1948 const DetailInputs& inputs = delegate_->RequestedFieldsForSection(section);
1949 for (DetailInputs::const_iterator it = inputs.begin();
1950 it != inputs.end(); ++it) {
1951 const DetailInput& input = *it;
1953 ui::ComboboxModel* input_model =
1954 delegate_->ComboboxModelForAutofillType(input.type);
1955 scoped_ptr<views::View> view_to_add;
1956 if (input_model) {
1957 views::Combobox* combobox = new views::Combobox(input_model);
1958 combobox->set_listener(this);
1959 comboboxes->insert(std::make_pair(input.type, combobox));
1960 SelectComboboxValueOrSetToDefault(combobox, input.initial_value);
1961 view_to_add.reset(combobox);
1962 } else {
1963 ExpandingTextfield* field = new ExpandingTextfield(input.initial_value,
1964 input.placeholder_text,
1965 input.IsMultiline(),
1966 this);
1967 textfields->insert(std::make_pair(input.type, field));
1968 view_to_add.reset(field);
1971 if (input.length == DetailInput::NONE) {
1972 other_owned_views_.push_back(view_to_add.release());
1973 continue;
1976 if (input.length == DetailInput::LONG)
1977 ++column_set_id;
1979 views::ColumnSet* column_set = layout->GetColumnSet(column_set_id);
1980 if (!column_set) {
1981 // Create a new column set and row.
1982 column_set = layout->AddColumnSet(column_set_id);
1983 if (it != inputs.begin())
1984 layout->AddPaddingRow(0, kManualInputRowPadding);
1985 layout->StartRow(0, column_set_id);
1986 } else {
1987 // Add a new column to existing row.
1988 column_set->AddPaddingColumn(0, views::kRelatedControlHorizontalSpacing);
1989 // Must explicitly skip the padding column since we've already started
1990 // adding views.
1991 layout->SkipColumns(1);
1994 float expand = input.expand_weight;
1995 column_set->AddColumn(views::GridLayout::FILL,
1996 views::GridLayout::FILL,
1997 expand ? expand : 1.0,
1998 views::GridLayout::USE_PREF,
2002 // This is the same as AddView(view_to_add), except that 1 is used for the
2003 // view's preferred width. Thus the width of the column completely depends
2004 // on |expand|.
2005 layout->AddView(view_to_add.release(), 1, 1,
2006 views::GridLayout::FILL, views::GridLayout::FILL,
2007 1, 0);
2009 if (input.length == DetailInput::LONG ||
2010 input.length == DetailInput::SHORT_EOL) {
2011 ++column_set_id;
2015 SetIconsForSection(section);
2018 void AutofillDialogViews::ShowDialogInMode(DialogMode dialog_mode) {
2019 loading_shield_->SetVisible(dialog_mode == LOADING);
2020 sign_in_web_view_->SetVisible(dialog_mode == SIGN_IN);
2021 notification_area_->SetVisible(dialog_mode == DETAIL_INPUT);
2022 scrollable_area_->SetVisible(dialog_mode == DETAIL_INPUT);
2023 FocusInitialView();
2026 void AutofillDialogViews::UpdateSectionImpl(
2027 DialogSection section,
2028 bool clobber_inputs) {
2029 DetailsGroup* group = GroupForSection(section);
2031 if (clobber_inputs) {
2032 ServerFieldType type = UNKNOWN_TYPE;
2033 views::View* focused = GetFocusManager()->GetFocusedView();
2034 if (focused && group->container->Contains(focused)) {
2035 // Remember which view was focused before the inputs are clobbered.
2036 if (focused->GetClassName() == ExpandingTextfield::kViewClassName)
2037 type = TypeForTextfield(focused);
2038 else if (focused->GetClassName() == views::Combobox::kViewClassName)
2039 type = TypeForCombobox(static_cast<views::Combobox*>(focused));
2042 InitInputsView(section);
2044 if (type != UNKNOWN_TYPE) {
2045 // Restore the focus to the input with the previous type (e.g. country).
2046 views::View* to_focus = TextfieldForType(type);
2047 if (!to_focus) to_focus = ComboboxForType(type);
2048 if (to_focus)
2049 to_focus->RequestFocus();
2051 } else {
2052 const DetailInputs& updated_inputs =
2053 delegate_->RequestedFieldsForSection(section);
2055 for (DetailInputs::const_iterator iter = updated_inputs.begin();
2056 iter != updated_inputs.end(); ++iter) {
2057 const DetailInput& input = *iter;
2059 TextfieldMap::iterator text_mapping = group->textfields.find(input.type);
2060 if (text_mapping != group->textfields.end()) {
2061 ExpandingTextfield* textfield = text_mapping->second;
2062 if (textfield->GetText().empty())
2063 textfield->SetText(input.initial_value);
2066 ComboboxMap::iterator combo_mapping = group->comboboxes.find(input.type);
2067 if (combo_mapping != group->comboboxes.end()) {
2068 views::Combobox* combobox = combo_mapping->second;
2069 if (combobox->selected_index() == combobox->model()->GetDefaultIndex())
2070 SelectComboboxValueOrSetToDefault(combobox, input.initial_value);
2074 SetIconsForSection(section);
2077 SetEditabilityForSection(section);
2078 UpdateDetailsGroupState(*group);
2081 void AutofillDialogViews::UpdateDetailsGroupState(const DetailsGroup& group) {
2082 const SuggestionState& suggestion_state =
2083 delegate_->SuggestionStateForSection(group.section);
2084 group.suggested_info->SetState(suggestion_state);
2085 group.manual_input->SetVisible(!suggestion_state.visible);
2087 UpdateButtonStripExtraView();
2089 const bool has_menu = !!delegate_->MenuModelForSection(group.section);
2091 if (group.suggested_button)
2092 group.suggested_button->SetVisible(has_menu);
2094 if (group.container) {
2095 group.container->SetForwardMouseEvents(
2096 has_menu && suggestion_state.visible);
2097 group.container->SetVisible(delegate_->SectionIsActive(group.section));
2098 if (group.container->visible())
2099 ValidateGroup(group, VALIDATE_EDIT);
2102 ContentsPreferredSizeChanged();
2105 void AutofillDialogViews::FocusInitialView() {
2106 views::View* to_focus = GetInitiallyFocusedView();
2107 if (to_focus && !to_focus->HasFocus())
2108 to_focus->RequestFocus();
2111 template<class T>
2112 void AutofillDialogViews::SetValidityForInput(
2113 T* input,
2114 const base::string16& message) {
2115 bool invalid = !message.empty();
2116 input->SetInvalid(invalid);
2118 if (invalid) {
2119 validity_map_[input] = message;
2120 } else {
2121 validity_map_.erase(input);
2123 if (error_bubble_ &&
2124 error_bubble_->anchor()->GetAncestorWithClassName(
2125 input->GetClassName()) == input) {
2126 validity_map_.erase(input);
2127 HideErrorBubble();
2132 void AutofillDialogViews::ShowErrorBubbleForViewIfNecessary(views::View* view) {
2133 if (!view->GetWidget())
2134 return;
2136 if (!delegate_->ShouldShowErrorBubble()) {
2137 DCHECK(!error_bubble_);
2138 return;
2141 if (view->GetClassName() == DecoratedTextfield::kViewClassName &&
2142 !static_cast<DecoratedTextfield*>(view)->invalid()) {
2143 return;
2146 views::View* input_view = GetAncestralInputView(view);
2147 std::map<views::View*, base::string16>::iterator error_message =
2148 validity_map_.find(input_view);
2149 if (error_message != validity_map_.end()) {
2150 input_view->ScrollRectToVisible(input_view->GetLocalBounds());
2152 if (!error_bubble_ || error_bubble_->anchor() != view) {
2153 HideErrorBubble();
2154 error_bubble_ = new InfoBubble(view, error_message->second);
2155 error_bubble_->set_align_to_anchor_edge(true);
2156 error_bubble_->set_preferred_width(
2157 (kSectionContainerWidth - views::kRelatedControlVerticalSpacing) / 2);
2158 bool show_above = view->GetClassName() == views::Combobox::kViewClassName;
2159 error_bubble_->set_show_above_anchor(show_above);
2160 error_bubble_->Show();
2161 observer_.Add(error_bubble_->GetWidget());
2166 void AutofillDialogViews::HideErrorBubble() {
2167 if (error_bubble_)
2168 error_bubble_->Hide();
2171 void AutofillDialogViews::MarkInputsInvalid(
2172 DialogSection section,
2173 const ValidityMessages& messages,
2174 bool overwrite_unsure) {
2175 DetailsGroup* group = GroupForSection(section);
2176 DCHECK(group->container->visible());
2178 if (group->manual_input->visible()) {
2179 for (TextfieldMap::const_iterator iter = group->textfields.begin();
2180 iter != group->textfields.end(); ++iter) {
2181 const ValidityMessage& message =
2182 messages.GetMessageOrDefault(iter->first);
2183 if (overwrite_unsure || message.sure)
2184 SetValidityForInput(iter->second, message.text);
2186 for (ComboboxMap::const_iterator iter = group->comboboxes.begin();
2187 iter != group->comboboxes.end(); ++iter) {
2188 const ValidityMessage& message =
2189 messages.GetMessageOrDefault(iter->first);
2190 if (overwrite_unsure || message.sure)
2191 SetValidityForInput(iter->second, message.text);
2193 } else {
2194 EraseInvalidViewsInGroup(group);
2196 if (section == GetCreditCardSection()) {
2197 // Special case CVC as it's not part of |group->manual_input|.
2198 const ValidityMessage& message =
2199 messages.GetMessageOrDefault(CREDIT_CARD_VERIFICATION_CODE);
2200 if (overwrite_unsure || message.sure) {
2201 SetValidityForInput(group->suggested_info->textfield(), message.text);
2207 bool AutofillDialogViews::ValidateGroup(const DetailsGroup& group,
2208 ValidationType validation_type) {
2209 DCHECK(group.container->visible());
2211 FieldValueMap detail_outputs;
2213 if (group.manual_input->visible()) {
2214 for (TextfieldMap::const_iterator iter = group.textfields.begin();
2215 iter != group.textfields.end(); ++iter) {
2216 if (!iter->second->editable())
2217 continue;
2219 detail_outputs[iter->first] = iter->second->GetText();
2221 for (ComboboxMap::const_iterator iter = group.comboboxes.begin();
2222 iter != group.comboboxes.end(); ++iter) {
2223 if (!iter->second->enabled())
2224 continue;
2226 views::Combobox* combobox = iter->second;
2227 base::string16 item =
2228 combobox->model()->GetItemAt(combobox->selected_index());
2229 detail_outputs[iter->first] = item;
2231 } else if (group.section == GetCreditCardSection()) {
2232 ExpandingTextfield* cvc = group.suggested_info->textfield();
2233 if (cvc->visible())
2234 detail_outputs[CREDIT_CARD_VERIFICATION_CODE] = cvc->GetText();
2237 ValidityMessages validity = delegate_->InputsAreValid(group.section,
2238 detail_outputs);
2239 MarkInputsInvalid(group.section, validity, validation_type == VALIDATE_FINAL);
2241 // If there are any validation errors, sure or unsure, the group is invalid.
2242 return !validity.HasErrors();
2245 bool AutofillDialogViews::ValidateForm() {
2246 bool all_valid = true;
2247 validity_map_.clear();
2249 for (DetailGroupMap::iterator iter = detail_groups_.begin();
2250 iter != detail_groups_.end(); ++iter) {
2251 const DetailsGroup& group = iter->second;
2252 if (!group.container->visible())
2253 continue;
2255 if (!ValidateGroup(group, VALIDATE_FINAL))
2256 all_valid = false;
2259 return all_valid;
2262 void AutofillDialogViews::InputEditedOrActivated(ServerFieldType type,
2263 const gfx::Rect& bounds,
2264 bool was_edit) {
2265 DCHECK_NE(UNKNOWN_TYPE, type);
2267 ExpandingTextfield* textfield = TextfieldForType(type);
2268 views::Combobox* combobox = ComboboxForType(type);
2270 // Both views may be NULL if the event comes from an inactive section, which
2271 // may occur when using an IME.
2272 if (!combobox && !textfield)
2273 return;
2275 DCHECK_NE(!!combobox, !!textfield);
2276 DetailsGroup* group = textfield ? GroupForView(textfield) :
2277 GroupForView(combobox);
2278 base::string16 text = textfield ?
2279 textfield->GetText() :
2280 combobox->model()->GetItemAt(combobox->selected_index());
2281 DCHECK(group);
2283 delegate_->UserEditedOrActivatedInput(group->section,
2284 type,
2285 GetWidget()->GetNativeView(),
2286 bounds,
2287 text,
2288 was_edit);
2290 // If the field is a textfield and is invalid, check if the text is now valid.
2291 // Many fields (i.e. CC#) are invalid for most of the duration of editing,
2292 // so flagging them as invalid prematurely is not helpful. However,
2293 // correcting a minor mistake (i.e. a wrong CC digit) should immediately
2294 // result in validation - positive user feedback.
2295 if (textfield && textfield->invalid() && was_edit) {
2296 SetValidityForInput(
2297 textfield,
2298 delegate_->InputValidityMessage(
2299 group->section, type, textfield->GetText()));
2301 // If the field transitioned from invalid to valid, re-validate the group,
2302 // since inter-field checks become meaningful with valid fields.
2303 if (!textfield->invalid())
2304 ValidateGroup(*group, VALIDATE_EDIT);
2307 if (delegate_->FieldControlsIcons(type))
2308 SetIconsForSection(group->section);
2310 SetEditabilityForSection(group->section);
2313 void AutofillDialogViews::UpdateButtonStripExtraView() {
2314 save_in_chrome_checkbox_container_->SetVisible(
2315 delegate_->ShouldOfferToSaveInChrome());
2317 gfx::Image image = delegate_->ButtonStripImage();
2318 button_strip_image_->SetVisible(!image.IsEmpty());
2319 button_strip_image_->SetImage(image.AsImageSkia());
2322 void AutofillDialogViews::ContentsPreferredSizeChanged() {
2323 if (updates_scope_ != 0) {
2324 needs_update_ = true;
2325 return;
2328 preferred_size_ = gfx::Size();
2330 if (GetWidget() && delegate_ && delegate_->GetWebContents()) {
2331 UpdateWebContentsModalDialogPosition(
2332 GetWidget(),
2333 WebContentsModalDialogManager::FromWebContents(
2334 delegate_->GetWebContents())->delegate()->
2335 GetWebContentsModalDialogHost());
2336 SetBoundsRect(bounds());
2340 AutofillDialogViews::DetailsGroup* AutofillDialogViews::GroupForSection(
2341 DialogSection section) {
2342 return &detail_groups_.find(section)->second;
2345 AutofillDialogViews::DetailsGroup* AutofillDialogViews::GroupForView(
2346 views::View* view) {
2347 DCHECK(view);
2349 views::View* input_view = GetAncestralInputView(view);
2350 if (!input_view)
2351 return NULL;
2353 for (DetailGroupMap::iterator iter = detail_groups_.begin();
2354 iter != detail_groups_.end(); ++iter) {
2355 DetailsGroup* group = &iter->second;
2356 if (input_view->parent() == group->manual_input)
2357 return group;
2359 // Textfields need to check a second case, since they can be suggested
2360 // inputs instead of directly editable inputs. Those are accessed via
2361 // |suggested_info|.
2362 if (input_view == group->suggested_info->textfield()) {
2363 return group;
2367 return NULL;
2370 void AutofillDialogViews::EraseInvalidViewsInGroup(const DetailsGroup* group) {
2371 std::map<views::View*, base::string16>::iterator it = validity_map_.begin();
2372 while (it != validity_map_.end()) {
2373 if (GroupForView(it->first) == group)
2374 validity_map_.erase(it++);
2375 else
2376 ++it;
2380 ExpandingTextfield* AutofillDialogViews::TextfieldForType(
2381 ServerFieldType type) {
2382 if (type == CREDIT_CARD_VERIFICATION_CODE) {
2383 DetailsGroup* group = GroupForSection(GetCreditCardSection());
2384 if (!group->manual_input->visible())
2385 return group->suggested_info->textfield();
2388 for (DetailGroupMap::iterator iter = detail_groups_.begin();
2389 iter != detail_groups_.end(); ++iter) {
2390 const DetailsGroup& group = iter->second;
2391 if (!delegate_->SectionIsActive(group.section))
2392 continue;
2394 TextfieldMap::const_iterator text_mapping = group.textfields.find(type);
2395 if (text_mapping != group.textfields.end())
2396 return text_mapping->second;
2399 return NULL;
2402 ServerFieldType AutofillDialogViews::TypeForTextfield(
2403 const views::View* textfield) {
2404 const views::View* expanding =
2405 textfield->GetAncestorWithClassName(ExpandingTextfield::kViewClassName);
2407 DetailsGroup* cc_group = GroupForSection(GetCreditCardSection());
2408 if (expanding == cc_group->suggested_info->textfield())
2409 return CREDIT_CARD_VERIFICATION_CODE;
2411 for (DetailGroupMap::const_iterator it = detail_groups_.begin();
2412 it != detail_groups_.end(); ++it) {
2413 if (!delegate_->SectionIsActive(it->second.section))
2414 continue;
2416 for (TextfieldMap::const_iterator text_it = it->second.textfields.begin();
2417 text_it != it->second.textfields.end(); ++text_it) {
2418 if (expanding == text_it->second)
2419 return text_it->first;
2423 return UNKNOWN_TYPE;
2426 views::Combobox* AutofillDialogViews::ComboboxForType(
2427 ServerFieldType type) {
2428 for (DetailGroupMap::iterator iter = detail_groups_.begin();
2429 iter != detail_groups_.end(); ++iter) {
2430 const DetailsGroup& group = iter->second;
2431 if (!delegate_->SectionIsActive(group.section))
2432 continue;
2434 ComboboxMap::const_iterator combo_mapping = group.comboboxes.find(type);
2435 if (combo_mapping != group.comboboxes.end())
2436 return combo_mapping->second;
2439 return NULL;
2442 ServerFieldType AutofillDialogViews::TypeForCombobox(
2443 const views::Combobox* combobox) const {
2444 for (DetailGroupMap::const_iterator it = detail_groups_.begin();
2445 it != detail_groups_.end(); ++it) {
2446 const DetailsGroup& group = it->second;
2447 if (!delegate_->SectionIsActive(group.section))
2448 continue;
2450 for (ComboboxMap::const_iterator combo_it = group.comboboxes.begin();
2451 combo_it != group.comboboxes.end(); ++combo_it) {
2452 if (combo_it->second == combobox)
2453 return combo_it->first;
2457 return UNKNOWN_TYPE;
2460 void AutofillDialogViews::DetailsContainerBoundsChanged() {
2461 if (error_bubble_)
2462 error_bubble_->UpdatePosition();
2465 void AutofillDialogViews::SetIconsForSection(DialogSection section) {
2466 FieldValueMap user_input;
2467 GetUserInput(section, &user_input);
2468 FieldIconMap field_icons = delegate_->IconsForFields(user_input);
2469 TextfieldMap* textfields = &GroupForSection(section)->textfields;
2470 for (TextfieldMap::const_iterator textfield_it = textfields->begin();
2471 textfield_it != textfields->end();
2472 ++textfield_it) {
2473 ServerFieldType field_type = textfield_it->first;
2474 FieldIconMap::const_iterator field_icon_it = field_icons.find(field_type);
2475 ExpandingTextfield* textfield = textfield_it->second;
2476 if (field_icon_it != field_icons.end())
2477 textfield->SetIcon(field_icon_it->second);
2478 else
2479 textfield->SetTooltipIcon(delegate_->TooltipForField(field_type));
2483 void AutofillDialogViews::SetEditabilityForSection(DialogSection section) {
2484 const DetailInputs& inputs =
2485 delegate_->RequestedFieldsForSection(section);
2486 DetailsGroup* group = GroupForSection(section);
2488 for (DetailInputs::const_iterator iter = inputs.begin();
2489 iter != inputs.end(); ++iter) {
2490 const DetailInput& input = *iter;
2491 bool editable = delegate_->InputIsEditable(input, section);
2493 TextfieldMap::iterator text_mapping = group->textfields.find(input.type);
2494 if (text_mapping != group->textfields.end()) {
2495 ExpandingTextfield* textfield= text_mapping->second;
2496 textfield->SetEditable(editable);
2497 continue;
2500 ComboboxMap::iterator combo_mapping = group->comboboxes.find(input.type);
2501 if (combo_mapping != group->comboboxes.end()) {
2502 views::Combobox* combobox = combo_mapping->second;
2503 combobox->SetEnabled(editable);
2508 void AutofillDialogViews::NonClientMousePressed() {
2509 delegate_->FocusMoved();
2512 AutofillDialogViews::DetailsGroup::DetailsGroup(DialogSection section)
2513 : section(section),
2514 container(NULL),
2515 manual_input(NULL),
2516 suggested_info(NULL),
2517 suggested_button(NULL) {}
2519 AutofillDialogViews::DetailsGroup::~DetailsGroup() {}
2521 } // namespace autofill