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"
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_view_delegate.h"
14 #include "chrome/browser/ui/autofill/loading_animation.h"
15 #include "chrome/browser/ui/views/autofill/expanding_textfield.h"
16 #include "chrome/browser/ui/views/autofill/info_bubble.h"
17 #include "chrome/browser/ui/views/autofill/tooltip_icon.h"
18 #include "components/autofill/core/browser/autofill_type.h"
19 #include "components/constrained_window/constrained_window_views.h"
20 #include "components/web_modal/web_contents_modal_dialog_host.h"
21 #include "components/web_modal/web_contents_modal_dialog_manager.h"
22 #include "components/web_modal/web_contents_modal_dialog_manager_delegate.h"
23 #include "content/public/browser/native_web_keyboard_event.h"
24 #include "content/public/browser/navigation_controller.h"
25 #include "content/public/browser/web_contents.h"
26 #include "grit/theme_resources.h"
27 #include "third_party/skia/include/core/SkColor.h"
28 #include "ui/base/models/combobox_model.h"
29 #include "ui/base/models/menu_model.h"
30 #include "ui/base/resource/resource_bundle.h"
31 #include "ui/compositor/paint_recorder.h"
32 #include "ui/events/event_handler.h"
33 #include "ui/gfx/animation/animation_delegate.h"
34 #include "ui/gfx/canvas.h"
35 #include "ui/gfx/color_utils.h"
36 #include "ui/gfx/font_list.h"
37 #include "ui/gfx/geometry/point.h"
38 #include "ui/gfx/path.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/painter.h"
63 #include "ui/views/view_targeter.h"
64 #include "ui/views/widget/widget.h"
65 #include "ui/views/window/dialog_client_view.h"
66 #include "ui/views/window/non_client_view.h"
72 // The width for the section container.
73 const int kSectionContainerWidth
= 440;
75 // The minimum useful height of the contents area of the dialog.
76 const int kMinimumContentsHeight
= 101;
78 // Horizontal padding between text and other elements (in pixels).
79 const int kAroundTextPadding
= 4;
81 // The space between the edges of a notification bar and the text within (in
83 const int kNotificationPadding
= 17;
85 // Vertical padding above and below each detail section (in pixels).
86 const int kDetailSectionVerticalPadding
= 10;
88 const int kArrowHeight
= 7;
89 const int kArrowWidth
= 2 * kArrowHeight
;
91 // The padding inside the edges of the dialog, in pixels.
92 const int kDialogEdgePadding
= 20;
94 // The vertical padding between rows of manual inputs (in pixels).
95 const int kManualInputRowPadding
= 10;
97 // The top and bottom padding, in pixels, for the suggestions menu dropdown
99 const int kMenuButtonTopInset
= 3;
100 const int kMenuButtonBottomInset
= 6;
102 const char kNotificationAreaClassName
[] = "autofill/NotificationArea";
103 const char kSectionContainerClassName
[] = "autofill/SectionContainer";
104 const char kSuggestedButtonClassName
[] = "autofill/SuggestedButton";
106 // Draws an arrow at the top of |canvas| pointing to |tip_x|.
107 void DrawArrow(gfx::Canvas
* canvas
,
109 const SkColor
& fill_color
,
110 const SkColor
& stroke_color
) {
111 const int arrow_half_width
= kArrowWidth
/ 2.0f
;
114 arrow
.moveTo(tip_x
- arrow_half_width
, kArrowHeight
);
115 arrow
.lineTo(tip_x
, 0);
116 arrow
.lineTo(tip_x
+ arrow_half_width
, kArrowHeight
);
119 fill_paint
.setColor(fill_color
);
120 canvas
->DrawPath(arrow
, fill_paint
);
122 if (stroke_color
!= SK_ColorTRANSPARENT
) {
123 SkPaint stroke_paint
;
124 stroke_paint
.setColor(stroke_color
);
125 stroke_paint
.setStyle(SkPaint::kStroke_Style
);
126 canvas
->DrawPath(arrow
, stroke_paint
);
130 void SelectComboboxValueOrSetToDefault(views::Combobox
* combobox
,
131 const base::string16
& value
) {
132 if (!combobox
->SelectValue(value
))
133 combobox
->SetSelectedIndex(combobox
->model()->GetDefaultIndex());
136 // This class handles layout for the first row of a SuggestionView.
137 // It exists to circumvent shortcomings of GridLayout and BoxLayout (namely that
138 // the former doesn't fully respect child visibility, and that the latter won't
139 // expand a single child).
140 class SectionRowView
: public views::View
{
142 SectionRowView() { SetBorder(views::Border::CreateEmptyBorder(10, 0, 0, 0)); }
144 ~SectionRowView() override
{}
146 // views::View implementation:
147 gfx::Size
GetPreferredSize() const override
{
150 for (int i
= 0; i
< child_count(); ++i
) {
151 if (child_at(i
)->visible()) {
153 width
+= kAroundTextPadding
;
155 gfx::Size size
= child_at(i
)->GetPreferredSize();
156 height
= std::max(height
, size
.height());
157 width
+= size
.width();
161 gfx::Insets insets
= GetInsets();
162 return gfx::Size(width
+ insets
.width(), height
+ insets
.height());
165 void Layout() override
{
166 const gfx::Rect bounds
= GetContentsBounds();
168 // Icon is left aligned.
169 int start_x
= bounds
.x();
170 views::View
* icon
= child_at(0);
171 if (icon
->visible()) {
172 icon
->SizeToPreferredSize();
174 icon
->SetY(bounds
.y() +
175 (bounds
.height() - icon
->bounds().height()) / 2);
176 start_x
+= icon
->bounds().width() + kAroundTextPadding
;
179 // Textfield is right aligned.
180 int end_x
= bounds
.width();
181 views::View
* textfield
= child_at(2);
182 if (textfield
->visible()) {
183 const int preferred_width
= textfield
->GetPreferredSize().width();
184 textfield
->SetBounds(bounds
.width() - preferred_width
, bounds
.y(),
185 preferred_width
, bounds
.height());
186 end_x
= textfield
->bounds().x() - kAroundTextPadding
;
189 // Label takes up all the space in between.
190 views::View
* label
= child_at(1);
191 if (label
->visible())
192 label
->SetBounds(start_x
, bounds
.y(), end_x
- start_x
, bounds
.height());
194 views::View::Layout();
198 DISALLOW_COPY_AND_ASSIGN(SectionRowView
);
201 // A view that propagates visibility and preferred size changes.
202 class LayoutPropagationView
: public views::View
{
204 LayoutPropagationView() {}
205 ~LayoutPropagationView() override
{}
208 void ChildVisibilityChanged(views::View
* child
) override
{
209 PreferredSizeChanged();
211 void ChildPreferredSizeChanged(views::View
* child
) override
{
212 PreferredSizeChanged();
216 DISALLOW_COPY_AND_ASSIGN(LayoutPropagationView
);
219 // A View for a single notification banner.
220 class NotificationView
: public views::View
,
221 public views::StyledLabelListener
{
223 NotificationView(const DialogNotification
& data
,
224 AutofillDialogViewDelegate
* delegate
)
228 scoped_ptr
<views::View
> label_view
;
229 scoped_ptr
<views::StyledLabel
> label(
230 new views::StyledLabel(data
.display_text(), this));
231 label
->set_auto_color_readability_enabled(false);
233 views::StyledLabel::RangeStyleInfo text_style
;
234 text_style
.color
= data
.GetTextColor();
236 if (data
.link_range().is_empty()) {
237 label
->AddStyleRange(gfx::Range(0, data
.display_text().size()),
240 gfx::Range
prefix_range(0, data
.link_range().start());
241 if (!prefix_range
.is_empty())
242 label
->AddStyleRange(prefix_range
, text_style
);
244 label
->AddStyleRange(data
.link_range(),
245 views::StyledLabel::RangeStyleInfo::CreateForLink());
247 gfx::Range
suffix_range(data
.link_range().end(),
248 data
.display_text().size());
249 if (!suffix_range
.is_empty())
250 label
->AddStyleRange(suffix_range
, text_style
);
252 label_view
.reset(label
.release());
254 AddChildView(label_view
.release());
256 if (!data
.tooltip_text().empty())
257 AddChildView(new TooltipIcon(data
.tooltip_text()));
260 views::Background::CreateSolidBackground(data
.GetBackgroundColor()));
261 SetBorder(views::Border::CreateSolidSidedBorder(
262 1, 0, 1, 0, data
.GetBorderColor()));
265 ~NotificationView() override
{}
267 views::Checkbox
* checkbox() {
271 // views::View implementation.
272 gfx::Insets
GetInsets() const override
{
273 int vertical_padding
= kNotificationPadding
;
275 vertical_padding
-= 3;
276 return gfx::Insets(vertical_padding
, kDialogEdgePadding
,
277 vertical_padding
, kDialogEdgePadding
);
280 int GetHeightForWidth(int width
) const override
{
281 int label_width
= width
- GetInsets().width();
282 if (child_count() > 1) {
283 const views::View
* tooltip_icon
= child_at(1);
284 label_width
-= tooltip_icon
->GetPreferredSize().width() +
288 return child_at(0)->GetHeightForWidth(label_width
) + GetInsets().height();
291 void Layout() override
{
292 // Surprisingly, GetContentsBounds() doesn't consult GetInsets().
293 gfx::Rect bounds
= GetLocalBounds();
294 bounds
.Inset(GetInsets());
295 int right_bound
= bounds
.right();
297 if (child_count() > 1) {
298 // The icon takes up the entire vertical space and an extra 20px on
299 // each side. This increases the hover target for the tooltip.
300 views::View
* tooltip_icon
= child_at(1);
301 gfx::Size icon_size
= tooltip_icon
->GetPreferredSize();
302 int icon_width
= icon_size
.width() + kDialogEdgePadding
;
303 right_bound
-= icon_width
;
304 tooltip_icon
->SetBounds(
306 icon_width
+ kDialogEdgePadding
, GetLocalBounds().height());
309 child_at(0)->SetBounds(bounds
.x(), bounds
.y(),
310 right_bound
- bounds
.x(), bounds
.height());
313 // views::StyledLabelListener implementation.
314 void StyledLabelLinkClicked(const gfx::Range
& range
,
315 int event_flags
) override
{
316 delegate_
->LinkClicked(data_
.link_url());
320 // The model data for this notification.
321 DialogNotification data_
;
323 // The delegate that handles interaction with |this|.
324 AutofillDialogViewDelegate
* delegate_
;
326 // The checkbox associated with this notification, or NULL if there is none.
327 views::Checkbox
* checkbox_
;
329 DISALLOW_COPY_AND_ASSIGN(NotificationView
);
332 // Gets either the Combobox or ExpandingTextfield that is an ancestor (including
334 views::View
* GetAncestralInputView(views::View
* view
) {
335 if (view
->GetClassName() == views::Combobox::kViewClassName
)
338 return view
->GetAncestorWithClassName(ExpandingTextfield::kViewClassName
);
341 // A class that informs |delegate_| when an unhandled mouse press occurs.
342 class MousePressedHandler
: public ui::EventHandler
{
344 explicit MousePressedHandler(AutofillDialogViewDelegate
* delegate
)
345 : delegate_(delegate
) {}
347 // ui::EventHandler implementation.
348 void OnMouseEvent(ui::MouseEvent
* event
) override
{
349 if (event
->type() == ui::ET_MOUSE_PRESSED
&& !event
->handled())
350 delegate_
->FocusMoved();
354 AutofillDialogViewDelegate
* const delegate_
;
356 DISALLOW_COPY_AND_ASSIGN(MousePressedHandler
);
361 // AutofillDialogViews::NotificationArea ---------------------------------------
363 AutofillDialogViews::NotificationArea::NotificationArea(
364 AutofillDialogViewDelegate
* delegate
)
365 : delegate_(delegate
) {
366 // Reserve vertical space for the arrow (regardless of whether one exists).
367 // The -1 accounts for the border.
368 SetBorder(views::Border::CreateEmptyBorder(kArrowHeight
- 1, 0, 0, 0));
370 views::BoxLayout
* box_layout
=
371 new views::BoxLayout(views::BoxLayout::kVertical
, 0, 0, 0);
372 SetLayoutManager(box_layout
);
375 AutofillDialogViews::NotificationArea::~NotificationArea() {}
377 void AutofillDialogViews::NotificationArea::SetNotifications(
378 const std::vector
<DialogNotification
>& notifications
) {
379 notifications_
= notifications
;
381 RemoveAllChildViews(true);
383 if (notifications_
.empty())
386 for (size_t i
= 0; i
< notifications_
.size(); ++i
) {
387 const DialogNotification
& notification
= notifications_
[i
];
388 scoped_ptr
<NotificationView
> view(new NotificationView(notification
,
391 AddChildView(view
.release());
394 PreferredSizeChanged();
397 gfx::Size
AutofillDialogViews::NotificationArea::GetPreferredSize() const {
398 gfx::Size size
= views::View::GetPreferredSize();
399 // Ensure that long notifications wrap and don't enlarge the dialog.
404 const char* AutofillDialogViews::NotificationArea::GetClassName() const {
405 return kNotificationAreaClassName
;
408 void AutofillDialogViews::NotificationArea::PaintChildren(
409 const ui::PaintContext
& context
) {
410 views::View::PaintChildren(context
);
412 ui::PaintRecorder
recorder(context
, size());
415 GetMirroredXInView(width() - arrow_centering_anchor_
->width() / 2.0f
),
416 notifications_
[0].GetBackgroundColor(),
417 notifications_
[0].GetBorderColor());
421 void AutofillDialogViews::OnWidgetDestroying(views::Widget
* widget
) {
422 if (widget
== window_
)
423 window_
->GetRootView()->RemovePostTargetHandler(event_handler_
.get());
426 void AutofillDialogViews::OnWidgetClosing(views::Widget
* widget
) {
427 observer_
.Remove(widget
);
428 if (error_bubble_
&& error_bubble_
->GetWidget() == widget
)
429 error_bubble_
= NULL
;
432 void AutofillDialogViews::OnWidgetBoundsChanged(views::Widget
* widget
,
433 const gfx::Rect
& new_bounds
) {
434 if (error_bubble_
&& error_bubble_
->GetWidget() == widget
)
439 bool AutofillDialogViews::NotificationArea::HasArrow() {
440 return !notifications_
.empty() && notifications_
[0].HasArrow() &&
441 arrow_centering_anchor_
.get();
444 // AutofillDialogViews::SectionContainer ---------------------------------------
446 AutofillDialogViews::SectionContainer::SectionContainer(
447 const base::string16
& label
,
448 views::View
* controls
,
449 views::Button
* proxy_button
)
450 : proxy_button_(proxy_button
),
451 forward_mouse_events_(false) {
452 set_notify_enter_exit_on_child(true);
454 SetBorder(views::Border::CreateEmptyBorder(kDetailSectionVerticalPadding
,
456 kDetailSectionVerticalPadding
,
457 kDialogEdgePadding
));
459 ui::ResourceBundle
& rb
= ui::ResourceBundle::GetSharedInstance();
460 views::Label
* label_view
= new views::Label(
461 label
, rb
.GetFontList(ui::ResourceBundle::BoldFont
));
462 label_view
->SetHorizontalAlignment(gfx::ALIGN_LEFT
);
464 views::View
* label_bar
= new views::View();
465 views::GridLayout
* label_bar_layout
= new views::GridLayout(label_bar
);
466 label_bar
->SetLayoutManager(label_bar_layout
);
467 const int kColumnSetId
= 0;
468 views::ColumnSet
* columns
= label_bar_layout
->AddColumnSet(kColumnSetId
);
470 views::GridLayout::LEADING
,
471 views::GridLayout::LEADING
,
473 views::GridLayout::FIXED
,
474 kSectionContainerWidth
- proxy_button
->GetPreferredSize().width(),
476 columns
->AddColumn(views::GridLayout::LEADING
,
477 views::GridLayout::LEADING
,
479 views::GridLayout::USE_PREF
,
482 label_bar_layout
->StartRow(0, kColumnSetId
);
483 label_bar_layout
->AddView(label_view
);
484 label_bar_layout
->AddView(proxy_button
);
486 SetLayoutManager(new views::BoxLayout(views::BoxLayout::kVertical
, 0, 0, 0));
487 AddChildView(label_bar
);
488 AddChildView(controls
);
491 scoped_ptr
<views::ViewTargeter
>(new views::ViewTargeter(this)));
494 AutofillDialogViews::SectionContainer::~SectionContainer() {}
496 void AutofillDialogViews::SectionContainer::SetActive(bool active
) {
497 bool is_active
= active
&& proxy_button_
->visible();
498 if (is_active
== !!background())
502 is_active
? views::Background::CreateSolidBackground(kLightShadingColor
)
507 void AutofillDialogViews::SectionContainer::SetForwardMouseEvents(
509 forward_mouse_events_
= forward
;
511 set_background(NULL
);
514 const char* AutofillDialogViews::SectionContainer::GetClassName() const {
515 return kSectionContainerClassName
;
518 void AutofillDialogViews::SectionContainer::OnMouseMoved(
519 const ui::MouseEvent
& event
) {
520 SetActive(ShouldForwardEvent(event
));
523 void AutofillDialogViews::SectionContainer::OnMouseEntered(
524 const ui::MouseEvent
& event
) {
525 if (!ShouldForwardEvent(event
))
529 proxy_button_
->OnMouseEntered(ProxyEvent(event
));
533 void AutofillDialogViews::SectionContainer::OnMouseExited(
534 const ui::MouseEvent
& event
) {
536 if (!ShouldForwardEvent(event
))
539 proxy_button_
->OnMouseExited(ProxyEvent(event
));
543 bool AutofillDialogViews::SectionContainer::OnMousePressed(
544 const ui::MouseEvent
& event
) {
545 if (!ShouldForwardEvent(event
))
548 return proxy_button_
->OnMousePressed(ProxyEvent(event
));
551 void AutofillDialogViews::SectionContainer::OnMouseReleased(
552 const ui::MouseEvent
& event
) {
553 if (!ShouldForwardEvent(event
))
556 proxy_button_
->OnMouseReleased(ProxyEvent(event
));
559 void AutofillDialogViews::SectionContainer::OnGestureEvent(
560 ui::GestureEvent
* event
) {
561 if (!ShouldForwardEvent(*event
))
564 proxy_button_
->OnGestureEvent(event
);
567 views::View
* AutofillDialogViews::SectionContainer::TargetForRect(
569 const gfx::Rect
& rect
) {
570 CHECK_EQ(root
, this);
571 views::View
* handler
= views::ViewTargeterDelegate::TargetForRect(root
, rect
);
573 // If the event is not in the label bar and there's no background to be
574 // cleared, let normal event handling take place.
576 rect
.CenterPoint().y() > child_at(0)->bounds().bottom()) {
580 // Special case for (CVC) inputs in the suggestion view.
581 if (forward_mouse_events_
&&
582 handler
->GetAncestorWithClassName(ExpandingTextfield::kViewClassName
)) {
586 // Special case for the proxy button itself.
587 if (handler
== proxy_button_
)
594 ui::MouseEvent
AutofillDialogViews::SectionContainer::ProxyEvent(
595 const ui::MouseEvent
& event
) {
596 ui::MouseEvent event_copy
= event
;
597 event_copy
.set_location(gfx::Point());
601 bool AutofillDialogViews::SectionContainer::ShouldForwardEvent(
602 const ui::LocatedEvent
& event
) {
603 // Always forward events on the label bar.
604 return forward_mouse_events_
|| event
.y() <= child_at(0)->bounds().bottom();
607 // AutofillDialogViews::SuggestedButton ----------------------------------------
609 AutofillDialogViews::SuggestedButton::SuggestedButton(
610 views::MenuButtonListener
* listener
)
611 : views::MenuButton(NULL
, base::string16(), listener
, false) {
612 const int kFocusBorderWidth
= 1;
613 SetBorder(views::Border::CreateEmptyBorder(kMenuButtonTopInset
,
615 kMenuButtonBottomInset
,
617 gfx::Insets insets
= GetInsets();
618 insets
+= gfx::Insets(-kFocusBorderWidth
, -kFocusBorderWidth
,
619 -kFocusBorderWidth
, -kFocusBorderWidth
);
621 views::Painter::CreateDashedFocusPainterWithInsets(insets
));
625 AutofillDialogViews::SuggestedButton::~SuggestedButton() {}
627 gfx::Size
AutofillDialogViews::SuggestedButton::GetPreferredSize() const {
628 ui::ResourceBundle
& rb
= ui::ResourceBundle::GetSharedInstance();
629 gfx::Size size
= rb
.GetImageNamed(ResourceIDForState()).Size();
630 const gfx::Insets insets
= GetInsets();
631 size
.Enlarge(insets
.width(), insets
.height());
635 const char* AutofillDialogViews::SuggestedButton::GetClassName() const {
636 return kSuggestedButtonClassName
;
639 void AutofillDialogViews::SuggestedButton::OnPaint(gfx::Canvas
* canvas
) {
640 ui::ResourceBundle
& rb
= ui::ResourceBundle::GetSharedInstance();
641 const gfx::Insets insets
= GetInsets();
642 canvas
->DrawImageInt(*rb
.GetImageSkiaNamed(ResourceIDForState()),
643 insets
.left(), insets
.top());
644 views::Painter::PaintFocusPainter(this, canvas
, focus_painter());
647 int AutofillDialogViews::SuggestedButton::ResourceIDForState() const {
648 views::Button::ButtonState button_state
= state();
649 if (button_state
== views::Button::STATE_PRESSED
)
650 return IDR_AUTOFILL_DIALOG_MENU_BUTTON_P
;
651 else if (button_state
== views::Button::STATE_HOVERED
)
652 return IDR_AUTOFILL_DIALOG_MENU_BUTTON_H
;
653 else if (button_state
== views::Button::STATE_DISABLED
)
654 return IDR_AUTOFILL_DIALOG_MENU_BUTTON_D
;
655 DCHECK_EQ(views::Button::STATE_NORMAL
, button_state
);
656 return IDR_AUTOFILL_DIALOG_MENU_BUTTON
;
659 // AutofillDialogViews::DetailsContainerView -----------------------------------
661 AutofillDialogViews::DetailsContainerView::DetailsContainerView(
662 const base::Closure
& callback
)
663 : bounds_changed_callback_(callback
),
664 ignore_layouts_(false) {}
666 AutofillDialogViews::DetailsContainerView::~DetailsContainerView() {}
668 void AutofillDialogViews::DetailsContainerView::OnBoundsChanged(
669 const gfx::Rect
& previous_bounds
) {
670 bounds_changed_callback_
.Run();
673 void AutofillDialogViews::DetailsContainerView::Layout() {
674 if (!ignore_layouts_
)
675 views::View::Layout();
678 // AutofillDialogViews::SuggestionView -----------------------------------------
680 AutofillDialogViews::SuggestionView::SuggestionView(
681 AutofillDialogViews
* autofill_dialog
)
682 : label_(new views::Label()),
683 label_line_2_(new views::Label()),
684 icon_(new views::ImageView()),
686 new ExpandingTextfield(base::string16(),
690 // TODO(estade): Make this the correct color.
691 SetBorder(views::Border::CreateSolidSidedBorder(1, 0, 0, 0, SK_ColorLTGRAY
));
693 SectionRowView
* label_container
= new SectionRowView();
694 AddChildView(label_container
);
697 label_container
->AddChildView(icon_
);
698 label_
->SetHorizontalAlignment(gfx::ALIGN_LEFT
);
699 label_container
->AddChildView(label_
);
701 // TODO(estade): get the sizing and spacing right on this textfield.
702 textfield_
->SetVisible(false);
703 textfield_
->SetDefaultWidthInCharacters(15);
704 label_container
->AddChildView(textfield_
);
706 label_line_2_
->SetHorizontalAlignment(gfx::ALIGN_LEFT
);
707 label_line_2_
->SetVisible(false);
708 label_line_2_
->SetLineHeight(22);
709 label_line_2_
->SetMultiLine(true);
710 AddChildView(label_line_2_
);
712 SetLayoutManager(new views::BoxLayout(views::BoxLayout::kVertical
, 0, 0, 7));
715 AutofillDialogViews::SuggestionView::~SuggestionView() {}
717 gfx::Size
AutofillDialogViews::SuggestionView::GetPreferredSize() const {
718 // There's no preferred width. The parent's layout should get the preferred
719 // height from GetHeightForWidth().
723 int AutofillDialogViews::SuggestionView::GetHeightForWidth(int width
) const {
725 CanUseVerticallyCompactText(width
, &height
);
729 bool AutofillDialogViews::SuggestionView::CanUseVerticallyCompactText(
731 int* resulting_height
) const {
732 // This calculation may be costly, avoid doing it more than once per width.
733 if (!calculated_heights_
.count(available_width
)) {
734 // Changing the state of |this| now will lead to extra layouts and
735 // paints we don't want, so create another SuggestionView to calculate
736 // which label we have room to show.
737 SuggestionView
sizing_view(NULL
);
738 sizing_view
.SetLabelText(state_
.vertically_compact_text
);
739 sizing_view
.SetIcon(state_
.icon
);
740 sizing_view
.SetTextfield(state_
.extra_text
, state_
.extra_icon
);
741 sizing_view
.label_
->SetSize(gfx::Size(available_width
, 0));
742 sizing_view
.label_line_2_
->SetSize(gfx::Size(available_width
, 0));
744 // Shortcut |sizing_view|'s GetHeightForWidth() to avoid an infinite loop.
745 // Its BoxLayout must do these calculations for us.
746 views::LayoutManager
* layout
= sizing_view
.GetLayoutManager();
747 if (layout
->GetPreferredSize(&sizing_view
).width() <= available_width
) {
748 calculated_heights_
[available_width
] = std::make_pair(
750 layout
->GetPreferredHeightForWidth(&sizing_view
, available_width
));
752 sizing_view
.SetLabelText(state_
.horizontally_compact_text
);
753 calculated_heights_
[available_width
] = std::make_pair(
755 layout
->GetPreferredHeightForWidth(&sizing_view
, available_width
));
759 const std::pair
<bool, int>& values
= calculated_heights_
[available_width
];
760 *resulting_height
= values
.second
;
764 void AutofillDialogViews::SuggestionView::OnBoundsChanged(
765 const gfx::Rect
& previous_bounds
) {
769 void AutofillDialogViews::SuggestionView::SetState(
770 const SuggestionState
& state
) {
771 calculated_heights_
.clear();
773 SetVisible(state_
.visible
);
775 SetIcon(state_
.icon
);
776 SetTextfield(state_
.extra_text
, state_
.extra_icon
);
777 PreferredSizeChanged();
780 void AutofillDialogViews::SuggestionView::SetLabelText(
781 const base::string16
& text
) {
782 // TODO(estade): does this localize well?
783 base::string16
line_return(base::ASCIIToUTF16("\n"));
784 size_t position
= text
.find(line_return
);
785 if (position
== base::string16::npos
) {
786 label_
->SetText(text
);
787 label_line_2_
->SetVisible(false);
789 label_
->SetText(text
.substr(0, position
));
790 label_line_2_
->SetText(text
.substr(position
+ line_return
.length()));
791 label_line_2_
->SetVisible(true);
795 void AutofillDialogViews::SuggestionView::SetIcon(
796 const gfx::Image
& image
) {
797 icon_
->SetVisible(!image
.IsEmpty());
798 icon_
->SetImage(image
.AsImageSkia());
801 void AutofillDialogViews::SuggestionView::SetTextfield(
802 const base::string16
& placeholder_text
,
803 const gfx::Image
& icon
) {
804 textfield_
->SetPlaceholderText(placeholder_text
);
805 textfield_
->SetIcon(icon
);
806 textfield_
->SetVisible(!placeholder_text
.empty());
809 void AutofillDialogViews::SuggestionView::UpdateLabelText() {
811 SetLabelText(CanUseVerticallyCompactText(width(), &unused
) ?
812 state_
.vertically_compact_text
:
813 state_
.horizontally_compact_text
);
816 // AutofillDialogView ----------------------------------------------------------
819 AutofillDialogView
* AutofillDialogView::Create(
820 AutofillDialogViewDelegate
* delegate
) {
821 return new AutofillDialogViews(delegate
);
824 // AutofillDialogViews ---------------------------------------------------------
826 AutofillDialogViews::AutofillDialogViews(AutofillDialogViewDelegate
* delegate
)
827 : delegate_(delegate
),
829 needs_update_(false),
831 notification_area_(NULL
),
832 scrollable_area_(NULL
),
833 details_container_(NULL
),
834 button_strip_extra_view_(NULL
),
835 save_in_chrome_checkbox_(NULL
),
836 save_in_chrome_checkbox_container_(NULL
),
837 focus_manager_(NULL
),
841 detail_groups_
.insert(std::make_pair(SECTION_CC
,
842 DetailsGroup(SECTION_CC
)));
843 detail_groups_
.insert(std::make_pair(SECTION_BILLING
,
844 DetailsGroup(SECTION_BILLING
)));
845 detail_groups_
.insert(std::make_pair(SECTION_SHIPPING
,
846 DetailsGroup(SECTION_SHIPPING
)));
849 AutofillDialogViews::~AutofillDialogViews() {
854 void AutofillDialogViews::Show() {
856 UpdateNotificationArea();
857 UpdateButtonStripExtraView();
859 window_
= constrained_window::ShowWebModalDialogViews(
860 this, delegate_
->GetWebContents());
861 focus_manager_
= window_
->GetFocusManager();
862 focus_manager_
->AddFocusChangeListener(this);
866 // Listen for size changes on the browser.
867 views::Widget
* browser_widget
=
868 views::Widget::GetTopLevelWidgetForNativeView(
869 delegate_
->GetWebContents()->GetNativeView());
870 observer_
.Add(browser_widget
);
872 // Listen for unhandled mouse presses on the non-client view.
873 event_handler_
.reset(new MousePressedHandler(delegate_
));
874 window_
->GetRootView()->AddPostTargetHandler(event_handler_
.get());
875 observer_
.Add(window_
);
878 void AutofillDialogViews::Hide() {
883 void AutofillDialogViews::UpdatesStarted() {
887 void AutofillDialogViews::UpdatesFinished() {
889 DCHECK_GE(updates_scope_
, 0);
890 if (updates_scope_
== 0 && needs_update_
) {
891 needs_update_
= false;
892 ContentsPreferredSizeChanged();
896 void AutofillDialogViews::UpdateButtonStrip() {
897 button_strip_extra_view_
->SetVisible(
898 GetDialogButtons() != ui::DIALOG_BUTTON_NONE
);
899 UpdateButtonStripExtraView();
900 GetDialogClientView()->UpdateDialogButtons();
902 ContentsPreferredSizeChanged();
905 void AutofillDialogViews::UpdateDetailArea() {
906 scrollable_area_
->SetVisible(true);
907 ContentsPreferredSizeChanged();
910 void AutofillDialogViews::UpdateForErrors() {
914 void AutofillDialogViews::UpdateNotificationArea() {
915 DCHECK(notification_area_
);
916 notification_area_
->SetNotifications(delegate_
->CurrentNotifications());
917 ContentsPreferredSizeChanged();
920 void AutofillDialogViews::UpdateSection(DialogSection section
) {
921 UpdateSectionImpl(section
, true);
924 void AutofillDialogViews::UpdateErrorBubble() {
925 if (!delegate_
->ShouldShowErrorBubble())
929 void AutofillDialogViews::FillSection(DialogSection section
,
930 ServerFieldType originating_type
) {
931 DetailsGroup
* group
= GroupForSection(section
);
932 // Make sure to overwrite the originating input if it exists.
933 TextfieldMap::iterator text_mapping
=
934 group
->textfields
.find(originating_type
);
935 if (text_mapping
!= group
->textfields
.end())
936 text_mapping
->second
->SetText(base::string16());
938 // If the Autofill data comes from a credit card, make sure to overwrite the
939 // CC comboboxes (even if they already have something in them). If the
940 // Autofill data comes from an AutofillProfile, leave the comboboxes alone.
941 if (section
== GetCreditCardSection() &&
942 AutofillType(originating_type
).group() == CREDIT_CARD
) {
943 for (ComboboxMap::const_iterator it
= group
->comboboxes
.begin();
944 it
!= group
->comboboxes
.end(); ++it
) {
945 if (AutofillType(it
->first
).group() == CREDIT_CARD
)
946 it
->second
->SetSelectedIndex(it
->second
->model()->GetDefaultIndex());
950 UpdateSectionImpl(section
, false);
953 void AutofillDialogViews::GetUserInput(DialogSection section
,
954 FieldValueMap
* output
) {
955 DetailsGroup
* group
= GroupForSection(section
);
956 for (TextfieldMap::const_iterator it
= group
->textfields
.begin();
957 it
!= group
->textfields
.end(); ++it
) {
958 output
->insert(std::make_pair(it
->first
, it
->second
->GetText()));
960 for (ComboboxMap::const_iterator it
= group
->comboboxes
.begin();
961 it
!= group
->comboboxes
.end(); ++it
) {
962 output
->insert(std::make_pair(it
->first
,
963 it
->second
->model()->GetItemAt(it
->second
->selected_index())));
967 base::string16
AutofillDialogViews::GetCvc() {
968 return GroupForSection(GetCreditCardSection())->suggested_info
->
969 textfield()->GetText();
972 bool AutofillDialogViews::SaveDetailsLocally() {
973 DCHECK(save_in_chrome_checkbox_
->visible());
974 return save_in_chrome_checkbox_
->checked();
977 void AutofillDialogViews::ModelChanged() {
978 menu_runner_
.reset();
980 for (DetailGroupMap::const_iterator iter
= detail_groups_
.begin();
981 iter
!= detail_groups_
.end(); ++iter
) {
982 UpdateDetailsGroupState(iter
->second
);
986 void AutofillDialogViews::ValidateSection(DialogSection section
) {
987 ValidateGroup(*GroupForSection(section
), VALIDATE_EDIT
);
990 gfx::Size
AutofillDialogViews::GetPreferredSize() const {
991 if (preferred_size_
.IsEmpty())
992 preferred_size_
= CalculatePreferredSize(false);
994 return preferred_size_
;
997 gfx::Size
AutofillDialogViews::GetMinimumSize() const {
998 return CalculatePreferredSize(true);
1001 void AutofillDialogViews::Layout() {
1002 const gfx::Rect content_bounds
= GetContentsBounds();
1003 const int x
= content_bounds
.x();
1004 const int y
= content_bounds
.y();
1005 const int width
= content_bounds
.width();
1006 // Layout notification area at top of dialog.
1007 int notification_height
= notification_area_
->GetHeightForWidth(width
);
1008 notification_area_
->SetBounds(x
, y
, width
, notification_height
);
1010 // The rest (the |scrollable_area_|) takes up whatever's left.
1011 if (scrollable_area_
->visible()) {
1013 if (notification_height
> notification_area_
->GetInsets().height())
1014 scroll_y
+= notification_height
+ views::kRelatedControlVerticalSpacing
;
1016 int scroll_bottom
= content_bounds
.bottom();
1017 DCHECK_EQ(scrollable_area_
->contents(), details_container_
);
1018 details_container_
->SizeToPreferredSize();
1019 details_container_
->Layout();
1020 // TODO(estade): remove this hack. See crbug.com/285996
1021 details_container_
->set_ignore_layouts(true);
1022 scrollable_area_
->SetBounds(x
, scroll_y
, width
, scroll_bottom
- scroll_y
);
1023 details_container_
->set_ignore_layouts(false);
1027 error_bubble_
->UpdatePosition();
1030 ui::ModalType
AutofillDialogViews::GetModalType() const {
1031 return ui::MODAL_TYPE_CHILD
;
1034 base::string16
AutofillDialogViews::GetWindowTitle() const {
1035 base::string16 title
= delegate_
->DialogTitle();
1036 // Hack alert: we don't want the dialog to jiggle when a title is added or
1037 // removed. Setting a non-empty string here keeps the dialog's title bar the
1039 return title
.empty() ? base::ASCIIToUTF16(" ") : title
;
1042 void AutofillDialogViews::WindowClosing() {
1043 focus_manager_
->RemoveFocusChangeListener(this);
1046 void AutofillDialogViews::DeleteDelegate() {
1048 // |this| belongs to the controller (|delegate_|).
1049 delegate_
->ViewClosed();
1052 int AutofillDialogViews::GetDialogButtons() const {
1053 return delegate_
->GetDialogButtons();
1056 int AutofillDialogViews::GetDefaultDialogButton() const {
1057 if (GetDialogButtons() & ui::DIALOG_BUTTON_OK
)
1058 return ui::DIALOG_BUTTON_OK
;
1060 return ui::DIALOG_BUTTON_NONE
;
1063 base::string16
AutofillDialogViews::GetDialogButtonLabel(
1064 ui::DialogButton button
) const {
1065 return button
== ui::DIALOG_BUTTON_OK
?
1066 delegate_
->ConfirmButtonText() : delegate_
->CancelButtonText();
1069 bool AutofillDialogViews::ShouldDefaultButtonBeBlue() const {
1073 bool AutofillDialogViews::IsDialogButtonEnabled(ui::DialogButton button
) const {
1074 return delegate_
->IsDialogButtonEnabled(button
);
1077 views::View
* AutofillDialogViews::GetInitiallyFocusedView() {
1078 if (!window_
|| !focus_manager_
)
1081 DCHECK(scrollable_area_
->visible());
1083 views::FocusManager
* manager
= focus_manager_
;
1084 for (views::View
* next
= scrollable_area_
;
1086 next
= manager
->GetNextFocusableView(next
, window_
, false, true)) {
1087 views::View
* input_view
= GetAncestralInputView(next
);
1091 // If there are no invalid inputs, return the first input found. Otherwise,
1092 // return the first invalid input found.
1093 if (validity_map_
.empty() ||
1094 validity_map_
.find(input_view
) != validity_map_
.end()) {
1099 return views::DialogDelegateView::GetInitiallyFocusedView();
1102 views::View
* AutofillDialogViews::CreateExtraView() {
1103 return button_strip_extra_view_
;
1106 bool AutofillDialogViews::Cancel() {
1107 delegate_
->OnCancel();
1111 bool AutofillDialogViews::Accept() {
1112 if (ValidateForm()) {
1113 delegate_
->OnAccept();
1115 // |ValidateForm()| failed; there should be invalid views in
1117 DCHECK(!validity_map_
.empty());
1124 void AutofillDialogViews::ContentsChanged(views::Textfield
* sender
,
1125 const base::string16
& new_contents
) {
1126 InputEditedOrActivated(TypeForTextfield(sender
),
1127 sender
->GetBoundsInScreen(),
1130 const ExpandingTextfield
* expanding
= static_cast<ExpandingTextfield
*>(
1131 sender
->GetAncestorWithClassName(ExpandingTextfield::kViewClassName
));
1132 if (expanding
&& expanding
->needs_layout())
1133 ContentsPreferredSizeChanged();
1136 bool AutofillDialogViews::HandleKeyEvent(views::Textfield
* sender
,
1137 const ui::KeyEvent
& key_event
) {
1138 content::NativeWebKeyboardEvent
event(key_event
);
1139 return delegate_
->HandleKeyPressEventInInput(event
);
1142 bool AutofillDialogViews::HandleMouseEvent(views::Textfield
* sender
,
1143 const ui::MouseEvent
& mouse_event
) {
1144 if (mouse_event
.IsLeftMouseButton() && sender
->HasFocus()) {
1145 InputEditedOrActivated(TypeForTextfield(sender
),
1146 sender
->GetBoundsInScreen(),
1148 // Show an error bubble if a user clicks on an input that's already focused
1150 ShowErrorBubbleForViewIfNecessary(sender
);
1156 void AutofillDialogViews::OnWillChangeFocus(
1157 views::View
* focused_before
,
1158 views::View
* focused_now
) {
1159 delegate_
->FocusMoved();
1163 void AutofillDialogViews::OnDidChangeFocus(
1164 views::View
* focused_before
,
1165 views::View
* focused_now
) {
1166 // If user leaves an edit-field, revalidate the group it belongs to.
1167 if (focused_before
) {
1168 DetailsGroup
* group
= GroupForView(focused_before
);
1169 if (group
&& group
->container
->visible())
1170 ValidateGroup(*group
, VALIDATE_EDIT
);
1173 // Show an error bubble when the user focuses the input.
1175 focused_now
->ScrollRectToVisible(focused_now
->GetLocalBounds());
1176 ShowErrorBubbleForViewIfNecessary(focused_now
);
1180 void AutofillDialogViews::OnPerformAction(views::Combobox
* combobox
) {
1181 DialogSection section
= GroupForView(combobox
)->section
;
1182 InputEditedOrActivated(TypeForCombobox(combobox
), gfx::Rect(), true);
1183 // NOTE: |combobox| may have been deleted.
1184 ValidateGroup(*GroupForSection(section
), VALIDATE_EDIT
);
1187 void AutofillDialogViews::OnMenuButtonClicked(views::View
* source
,
1188 const gfx::Point
& point
) {
1189 DCHECK_EQ(kSuggestedButtonClassName
, source
->GetClassName());
1191 DetailsGroup
* group
= NULL
;
1192 for (DetailGroupMap::iterator iter
= detail_groups_
.begin();
1193 iter
!= detail_groups_
.end(); ++iter
) {
1194 if (source
== iter
->second
.suggested_button
) {
1195 group
= &iter
->second
;
1201 if (!group
->suggested_button
->visible())
1205 new views::MenuRunner(delegate_
->MenuModelForSection(group
->section
), 0));
1207 group
->container
->SetActive(true);
1209 gfx::Rect screen_bounds
= source
->GetBoundsInScreen();
1210 screen_bounds
.Inset(source
->GetInsets());
1211 if (menu_runner_
->RunMenuAt(source
->GetWidget(),
1212 group
->suggested_button
,
1214 views::MENU_ANCHOR_TOPRIGHT
,
1215 ui::MENU_SOURCE_NONE
) ==
1216 views::MenuRunner::MENU_DELETED
) {
1220 group
->container
->SetActive(false);
1223 gfx::Size
AutofillDialogViews::CalculatePreferredSize(
1224 bool get_minimum_size
) const {
1225 gfx::Insets insets
= GetInsets();
1226 gfx::Size scroll_size
= scrollable_area_
->contents()->GetPreferredSize();
1227 // The width is always set by the scroll area.
1228 const int width
= scroll_size
.width();
1231 const int notification_height
= notification_area_
->GetHeightForWidth(width
);
1232 if (notification_height
> notification_area_
->GetInsets().height())
1233 height
+= notification_height
+ views::kRelatedControlVerticalSpacing
;
1235 if (scrollable_area_
->visible())
1236 height
+= get_minimum_size
? kMinimumContentsHeight
: scroll_size
.height();
1238 return gfx::Size(width
+ insets
.width(), height
+ insets
.height());
1241 gfx::Size
AutofillDialogViews::GetMinimumSignInViewSize() const {
1242 return gfx::Size(GetDialogClientView()->size().width() - GetInsets().width(),
1243 kMinimumContentsHeight
);
1246 gfx::Size
AutofillDialogViews::GetMaximumSignInViewSize() const {
1247 web_modal::WebContentsModalDialogHost
* dialog_host
=
1248 web_modal::WebContentsModalDialogManager::FromWebContents(
1249 delegate_
->GetWebContents())->delegate()->
1250 GetWebContentsModalDialogHost();
1252 // Inset the maximum dialog height to get the maximum content height.
1253 int height
= dialog_host
->GetMaximumDialogSize().height();
1254 const int non_client_height
= GetWidget()->non_client_view()->height();
1255 const int client_height
= GetWidget()->client_view()->height();
1256 // TODO(msw): Resolve the 12 pixel discrepancy; is that the bubble border?
1257 height
-= non_client_height
- client_height
- 12;
1258 height
= std::max(height
, kMinimumContentsHeight
);
1260 // The dialog's width never changes.
1261 const int width
= GetDialogClientView()->size().width() - GetInsets().width();
1262 return gfx::Size(width
, height
);
1265 // TODO(estade): remove.
1266 DialogSection
AutofillDialogViews::GetCreditCardSection() const {
1270 void AutofillDialogViews::InitChildViews() {
1271 button_strip_extra_view_
= new LayoutPropagationView();
1272 button_strip_extra_view_
->SetLayoutManager(
1273 new views::BoxLayout(views::BoxLayout::kHorizontal
, 0, 0, 0));
1275 save_in_chrome_checkbox_container_
= new views::View();
1276 save_in_chrome_checkbox_container_
->SetLayoutManager(
1277 new views::BoxLayout(views::BoxLayout::kHorizontal
, 0, 0, 7));
1278 button_strip_extra_view_
->AddChildView(save_in_chrome_checkbox_container_
);
1280 save_in_chrome_checkbox_
=
1281 new views::Checkbox(delegate_
->SaveLocallyText());
1282 save_in_chrome_checkbox_
->SetChecked(delegate_
->ShouldSaveInChrome());
1283 save_in_chrome_checkbox_container_
->AddChildView(save_in_chrome_checkbox_
);
1285 save_in_chrome_checkbox_container_
->AddChildView(
1286 new TooltipIcon(delegate_
->SaveLocallyTooltip()));
1288 notification_area_
= new NotificationArea(delegate_
);
1289 AddChildView(notification_area_
);
1291 scrollable_area_
= new views::ScrollView();
1292 scrollable_area_
->set_hide_horizontal_scrollbar(true);
1293 scrollable_area_
->SetContents(CreateDetailsContainer());
1294 AddChildView(scrollable_area_
);
1297 views::View
* AutofillDialogViews::CreateDetailsContainer() {
1298 details_container_
= new DetailsContainerView(
1299 base::Bind(&AutofillDialogViews::DetailsContainerBoundsChanged
,
1300 base::Unretained(this)));
1302 // A box layout is used because it respects widget visibility.
1303 details_container_
->SetLayoutManager(
1304 new views::BoxLayout(views::BoxLayout::kVertical
, 0, 0, 0));
1305 for (DetailGroupMap::iterator iter
= detail_groups_
.begin();
1306 iter
!= detail_groups_
.end(); ++iter
) {
1307 CreateDetailsSection(iter
->second
.section
);
1308 details_container_
->AddChildView(iter
->second
.container
);
1311 return details_container_
;
1314 void AutofillDialogViews::CreateDetailsSection(DialogSection section
) {
1315 // Inputs container (manual inputs + combobox).
1316 views::View
* inputs_container
= CreateInputsContainer(section
);
1318 DetailsGroup
* group
= GroupForSection(section
);
1319 // Container (holds label + inputs).
1320 group
->container
= new SectionContainer(delegate_
->LabelForSection(section
),
1322 group
->suggested_button
);
1323 DCHECK(group
->suggested_button
->parent());
1324 UpdateDetailsGroupState(*group
);
1327 views::View
* AutofillDialogViews::CreateInputsContainer(DialogSection section
) {
1328 // The |info_view| holds |manual_inputs| and |suggested_info|, allowing the
1329 // dialog to toggle which is shown.
1330 views::View
* info_view
= new views::View();
1331 info_view
->SetLayoutManager(
1332 new views::BoxLayout(views::BoxLayout::kVertical
, 0, 0, 0));
1334 DetailsGroup
* group
= GroupForSection(section
);
1335 group
->manual_input
= new views::View();
1336 InitInputsView(section
);
1337 info_view
->AddChildView(group
->manual_input
);
1339 group
->suggested_info
= new SuggestionView(this);
1340 info_view
->AddChildView(group
->suggested_info
);
1342 // TODO(estade): It might be slightly more OO if this button were created
1343 // and listened to by the section container.
1344 group
->suggested_button
= new SuggestedButton(this);
1349 // TODO(estade): we should be using Chrome-style constrained window padding
1351 void AutofillDialogViews::InitInputsView(DialogSection section
) {
1352 DetailsGroup
* group
= GroupForSection(section
);
1353 EraseInvalidViewsInGroup(group
);
1355 TextfieldMap
* textfields
= &group
->textfields
;
1356 textfields
->clear();
1358 ComboboxMap
* comboboxes
= &group
->comboboxes
;
1359 comboboxes
->clear();
1361 views::View
* view
= group
->manual_input
;
1362 view
->RemoveAllChildViews(true);
1364 views::GridLayout
* layout
= new views::GridLayout(view
);
1365 view
->SetLayoutManager(layout
);
1367 int column_set_id
= 0;
1368 const DetailInputs
& inputs
= delegate_
->RequestedFieldsForSection(section
);
1369 for (DetailInputs::const_iterator it
= inputs
.begin();
1370 it
!= inputs
.end(); ++it
) {
1371 const DetailInput
& input
= *it
;
1373 ui::ComboboxModel
* input_model
=
1374 delegate_
->ComboboxModelForAutofillType(input
.type
);
1375 scoped_ptr
<views::View
> view_to_add
;
1377 views::Combobox
* combobox
= new views::Combobox(input_model
);
1378 combobox
->set_listener(this);
1379 comboboxes
->insert(std::make_pair(input
.type
, combobox
));
1380 SelectComboboxValueOrSetToDefault(combobox
, input
.initial_value
);
1381 view_to_add
.reset(combobox
);
1383 ExpandingTextfield
* field
= new ExpandingTextfield(input
.initial_value
,
1384 input
.placeholder_text
,
1385 input
.IsMultiline(),
1387 textfields
->insert(std::make_pair(input
.type
, field
));
1388 view_to_add
.reset(field
);
1391 if (input
.length
== DetailInput::NONE
) {
1392 other_owned_views_
.push_back(view_to_add
.release());
1396 if (input
.length
== DetailInput::LONG
)
1399 views::ColumnSet
* column_set
= layout
->GetColumnSet(column_set_id
);
1401 // Create a new column set and row.
1402 column_set
= layout
->AddColumnSet(column_set_id
);
1403 if (it
!= inputs
.begin())
1404 layout
->AddPaddingRow(0, kManualInputRowPadding
);
1405 layout
->StartRow(0, column_set_id
);
1407 // Add a new column to existing row.
1408 column_set
->AddPaddingColumn(0, views::kRelatedControlHorizontalSpacing
);
1409 // Must explicitly skip the padding column since we've already started
1411 layout
->SkipColumns(1);
1414 float expand
= input
.expand_weight
;
1415 column_set
->AddColumn(views::GridLayout::FILL
,
1416 views::GridLayout::FILL
,
1417 expand
? expand
: 1.0,
1418 views::GridLayout::USE_PREF
,
1422 // This is the same as AddView(view_to_add), except that 1 is used for the
1423 // view's preferred width. Thus the width of the column completely depends
1425 layout
->AddView(view_to_add
.release(), 1, 1,
1426 views::GridLayout::FILL
, views::GridLayout::FILL
,
1429 if (input
.length
== DetailInput::LONG
||
1430 input
.length
== DetailInput::SHORT_EOL
) {
1435 SetIconsForSection(section
);
1438 void AutofillDialogViews::UpdateSectionImpl(
1439 DialogSection section
,
1440 bool clobber_inputs
) {
1441 DetailsGroup
* group
= GroupForSection(section
);
1443 if (clobber_inputs
) {
1444 ServerFieldType type
= UNKNOWN_TYPE
;
1445 views::View
* focused
= GetFocusManager()->GetFocusedView();
1446 if (focused
&& group
->container
->Contains(focused
)) {
1447 // Remember which view was focused before the inputs are clobbered.
1448 if (focused
->GetClassName() == ExpandingTextfield::kViewClassName
)
1449 type
= TypeForTextfield(focused
);
1450 else if (focused
->GetClassName() == views::Combobox::kViewClassName
)
1451 type
= TypeForCombobox(static_cast<views::Combobox
*>(focused
));
1454 InitInputsView(section
);
1456 if (type
!= UNKNOWN_TYPE
) {
1457 // Restore the focus to the input with the previous type (e.g. country).
1458 views::View
* to_focus
= TextfieldForType(type
);
1459 if (!to_focus
) to_focus
= ComboboxForType(type
);
1461 to_focus
->RequestFocus();
1464 const DetailInputs
& updated_inputs
=
1465 delegate_
->RequestedFieldsForSection(section
);
1467 for (DetailInputs::const_iterator iter
= updated_inputs
.begin();
1468 iter
!= updated_inputs
.end(); ++iter
) {
1469 const DetailInput
& input
= *iter
;
1471 TextfieldMap::iterator text_mapping
= group
->textfields
.find(input
.type
);
1472 if (text_mapping
!= group
->textfields
.end()) {
1473 ExpandingTextfield
* textfield
= text_mapping
->second
;
1474 if (textfield
->GetText().empty())
1475 textfield
->SetText(input
.initial_value
);
1478 ComboboxMap::iterator combo_mapping
= group
->comboboxes
.find(input
.type
);
1479 if (combo_mapping
!= group
->comboboxes
.end()) {
1480 views::Combobox
* combobox
= combo_mapping
->second
;
1481 if (combobox
->selected_index() == combobox
->model()->GetDefaultIndex())
1482 SelectComboboxValueOrSetToDefault(combobox
, input
.initial_value
);
1486 SetIconsForSection(section
);
1489 UpdateDetailsGroupState(*group
);
1492 void AutofillDialogViews::UpdateDetailsGroupState(const DetailsGroup
& group
) {
1493 const SuggestionState
& suggestion_state
=
1494 delegate_
->SuggestionStateForSection(group
.section
);
1495 group
.suggested_info
->SetState(suggestion_state
);
1496 group
.manual_input
->SetVisible(!suggestion_state
.visible
);
1498 UpdateButtonStripExtraView();
1500 const bool has_menu
= !!delegate_
->MenuModelForSection(group
.section
);
1502 if (group
.suggested_button
)
1503 group
.suggested_button
->SetVisible(has_menu
);
1505 if (group
.container
) {
1506 group
.container
->SetForwardMouseEvents(
1507 has_menu
&& suggestion_state
.visible
);
1508 group
.container
->SetVisible(delegate_
->SectionIsActive(group
.section
));
1509 if (group
.container
->visible())
1510 ValidateGroup(group
, VALIDATE_EDIT
);
1513 ContentsPreferredSizeChanged();
1516 void AutofillDialogViews::FocusInitialView() {
1517 views::View
* to_focus
= GetInitiallyFocusedView();
1518 if (to_focus
&& !to_focus
->HasFocus())
1519 to_focus
->RequestFocus();
1523 void AutofillDialogViews::SetValidityForInput(
1525 const base::string16
& message
) {
1526 bool invalid
= !message
.empty();
1527 input
->SetInvalid(invalid
);
1530 validity_map_
[input
] = message
;
1532 validity_map_
.erase(input
);
1534 if (error_bubble_
&&
1535 error_bubble_
->anchor()->GetAncestorWithClassName(
1536 input
->GetClassName()) == input
) {
1537 validity_map_
.erase(input
);
1543 void AutofillDialogViews::ShowErrorBubbleForViewIfNecessary(views::View
* view
) {
1544 if (!view
->GetWidget())
1547 if (!delegate_
->ShouldShowErrorBubble()) {
1548 DCHECK(!error_bubble_
);
1552 if (view
->GetClassName() == DecoratedTextfield::kViewClassName
&&
1553 !static_cast<DecoratedTextfield
*>(view
)->invalid()) {
1557 views::View
* input_view
= GetAncestralInputView(view
);
1558 std::map
<views::View
*, base::string16
>::iterator error_message
=
1559 validity_map_
.find(input_view
);
1560 if (error_message
!= validity_map_
.end()) {
1561 input_view
->ScrollRectToVisible(input_view
->GetLocalBounds());
1563 if (!error_bubble_
|| error_bubble_
->anchor() != view
) {
1565 error_bubble_
= new InfoBubble(view
, error_message
->second
);
1566 error_bubble_
->set_align_to_anchor_edge(true);
1567 error_bubble_
->set_preferred_width(
1568 (kSectionContainerWidth
- views::kRelatedControlVerticalSpacing
) / 2);
1569 bool show_above
= view
->GetClassName() == views::Combobox::kViewClassName
;
1570 error_bubble_
->set_show_above_anchor(show_above
);
1571 error_bubble_
->Show();
1572 observer_
.Add(error_bubble_
->GetWidget());
1577 void AutofillDialogViews::HideErrorBubble() {
1579 error_bubble_
->Hide();
1582 void AutofillDialogViews::MarkInputsInvalid(
1583 DialogSection section
,
1584 const ValidityMessages
& messages
,
1585 bool overwrite_unsure
) {
1586 DetailsGroup
* group
= GroupForSection(section
);
1587 DCHECK(group
->container
->visible());
1589 if (group
->manual_input
->visible()) {
1590 for (TextfieldMap::const_iterator iter
= group
->textfields
.begin();
1591 iter
!= group
->textfields
.end(); ++iter
) {
1592 const ValidityMessage
& message
=
1593 messages
.GetMessageOrDefault(iter
->first
);
1594 if (overwrite_unsure
|| message
.sure
)
1595 SetValidityForInput(iter
->second
, message
.text
);
1597 for (ComboboxMap::const_iterator iter
= group
->comboboxes
.begin();
1598 iter
!= group
->comboboxes
.end(); ++iter
) {
1599 const ValidityMessage
& message
=
1600 messages
.GetMessageOrDefault(iter
->first
);
1601 if (overwrite_unsure
|| message
.sure
)
1602 SetValidityForInput(iter
->second
, message
.text
);
1605 EraseInvalidViewsInGroup(group
);
1607 if (section
== GetCreditCardSection()) {
1608 // Special case CVC as it's not part of |group->manual_input|.
1609 const ValidityMessage
& message
=
1610 messages
.GetMessageOrDefault(CREDIT_CARD_VERIFICATION_CODE
);
1611 if (overwrite_unsure
|| message
.sure
) {
1612 SetValidityForInput(group
->suggested_info
->textfield(), message
.text
);
1618 bool AutofillDialogViews::ValidateGroup(const DetailsGroup
& group
,
1619 ValidationType validation_type
) {
1620 DCHECK(group
.container
->visible());
1622 FieldValueMap detail_outputs
;
1624 if (group
.manual_input
->visible()) {
1625 for (TextfieldMap::const_iterator iter
= group
.textfields
.begin();
1626 iter
!= group
.textfields
.end(); ++iter
) {
1627 if (!iter
->second
->editable())
1630 detail_outputs
[iter
->first
] = iter
->second
->GetText();
1632 for (ComboboxMap::const_iterator iter
= group
.comboboxes
.begin();
1633 iter
!= group
.comboboxes
.end(); ++iter
) {
1634 if (!iter
->second
->enabled())
1637 views::Combobox
* combobox
= iter
->second
;
1638 base::string16 item
=
1639 combobox
->model()->GetItemAt(combobox
->selected_index());
1640 detail_outputs
[iter
->first
] = item
;
1642 } else if (group
.section
== GetCreditCardSection()) {
1643 ExpandingTextfield
* cvc
= group
.suggested_info
->textfield();
1645 detail_outputs
[CREDIT_CARD_VERIFICATION_CODE
] = cvc
->GetText();
1648 ValidityMessages validity
= delegate_
->InputsAreValid(group
.section
,
1650 MarkInputsInvalid(group
.section
, validity
, validation_type
== VALIDATE_FINAL
);
1652 // If there are any validation errors, sure or unsure, the group is invalid.
1653 return !validity
.HasErrors();
1656 bool AutofillDialogViews::ValidateForm() {
1657 bool all_valid
= true;
1658 validity_map_
.clear();
1660 for (DetailGroupMap::iterator iter
= detail_groups_
.begin();
1661 iter
!= detail_groups_
.end(); ++iter
) {
1662 const DetailsGroup
& group
= iter
->second
;
1663 if (!group
.container
->visible())
1666 if (!ValidateGroup(group
, VALIDATE_FINAL
))
1673 void AutofillDialogViews::InputEditedOrActivated(ServerFieldType type
,
1674 const gfx::Rect
& bounds
,
1676 DCHECK_NE(UNKNOWN_TYPE
, type
);
1678 ExpandingTextfield
* textfield
= TextfieldForType(type
);
1679 views::Combobox
* combobox
= ComboboxForType(type
);
1681 // Both views may be NULL if the event comes from an inactive section, which
1682 // may occur when using an IME.
1683 if (!combobox
&& !textfield
)
1686 DCHECK_NE(!!combobox
, !!textfield
);
1687 DetailsGroup
* group
= textfield
? GroupForView(textfield
) :
1688 GroupForView(combobox
);
1689 base::string16 text
= textfield
?
1690 textfield
->GetText() :
1691 combobox
->model()->GetItemAt(combobox
->selected_index());
1694 delegate_
->UserEditedOrActivatedInput(group
->section
,
1696 GetWidget()->GetNativeView(),
1701 // If the field is a textfield and is invalid, check if the text is now valid.
1702 // Many fields (i.e. CC#) are invalid for most of the duration of editing,
1703 // so flagging them as invalid prematurely is not helpful. However,
1704 // correcting a minor mistake (i.e. a wrong CC digit) should immediately
1705 // result in validation - positive user feedback.
1706 if (textfield
&& textfield
->invalid() && was_edit
) {
1707 SetValidityForInput(
1709 delegate_
->InputValidityMessage(
1710 group
->section
, type
, textfield
->GetText()));
1712 // If the field transitioned from invalid to valid, re-validate the group,
1713 // since inter-field checks become meaningful with valid fields.
1714 if (!textfield
->invalid())
1715 ValidateGroup(*group
, VALIDATE_EDIT
);
1718 if (delegate_
->FieldControlsIcons(type
))
1719 SetIconsForSection(group
->section
);
1722 void AutofillDialogViews::UpdateButtonStripExtraView() {
1723 save_in_chrome_checkbox_container_
->SetVisible(
1724 delegate_
->ShouldOfferToSaveInChrome());
1727 void AutofillDialogViews::ContentsPreferredSizeChanged() {
1728 if (updates_scope_
!= 0) {
1729 needs_update_
= true;
1733 preferred_size_
= gfx::Size();
1735 if (GetWidget() && delegate_
&& delegate_
->GetWebContents()) {
1736 constrained_window::UpdateWebContentsModalDialogPosition(
1738 web_modal::WebContentsModalDialogManager::FromWebContents(
1739 delegate_
->GetWebContents())->delegate()->
1740 GetWebContentsModalDialogHost());
1741 SetBoundsRect(bounds());
1745 AutofillDialogViews::DetailsGroup
* AutofillDialogViews::GroupForSection(
1746 DialogSection section
) {
1747 return &detail_groups_
.find(section
)->second
;
1750 AutofillDialogViews::DetailsGroup
* AutofillDialogViews::GroupForView(
1751 views::View
* view
) {
1754 views::View
* input_view
= GetAncestralInputView(view
);
1758 for (DetailGroupMap::iterator iter
= detail_groups_
.begin();
1759 iter
!= detail_groups_
.end(); ++iter
) {
1760 DetailsGroup
* group
= &iter
->second
;
1761 if (input_view
->parent() == group
->manual_input
)
1764 // Textfields need to check a second case, since they can be suggested
1765 // inputs instead of directly editable inputs. Those are accessed via
1766 // |suggested_info|.
1767 if (input_view
== group
->suggested_info
->textfield()) {
1775 void AutofillDialogViews::EraseInvalidViewsInGroup(const DetailsGroup
* group
) {
1776 std::map
<views::View
*, base::string16
>::iterator it
= validity_map_
.begin();
1777 while (it
!= validity_map_
.end()) {
1778 if (GroupForView(it
->first
) == group
)
1779 validity_map_
.erase(it
++);
1785 ExpandingTextfield
* AutofillDialogViews::TextfieldForType(
1786 ServerFieldType type
) {
1787 if (type
== CREDIT_CARD_VERIFICATION_CODE
) {
1788 DetailsGroup
* group
= GroupForSection(GetCreditCardSection());
1789 if (!group
->manual_input
->visible())
1790 return group
->suggested_info
->textfield();
1793 for (DetailGroupMap::iterator iter
= detail_groups_
.begin();
1794 iter
!= detail_groups_
.end(); ++iter
) {
1795 const DetailsGroup
& group
= iter
->second
;
1796 if (!delegate_
->SectionIsActive(group
.section
))
1799 TextfieldMap::const_iterator text_mapping
= group
.textfields
.find(type
);
1800 if (text_mapping
!= group
.textfields
.end())
1801 return text_mapping
->second
;
1807 ServerFieldType
AutofillDialogViews::TypeForTextfield(
1808 const views::View
* textfield
) {
1809 const views::View
* expanding
=
1810 textfield
->GetAncestorWithClassName(ExpandingTextfield::kViewClassName
);
1812 DetailsGroup
* cc_group
= GroupForSection(GetCreditCardSection());
1813 if (expanding
== cc_group
->suggested_info
->textfield())
1814 return CREDIT_CARD_VERIFICATION_CODE
;
1816 for (DetailGroupMap::const_iterator it
= detail_groups_
.begin();
1817 it
!= detail_groups_
.end(); ++it
) {
1818 if (!delegate_
->SectionIsActive(it
->second
.section
))
1821 for (TextfieldMap::const_iterator text_it
= it
->second
.textfields
.begin();
1822 text_it
!= it
->second
.textfields
.end(); ++text_it
) {
1823 if (expanding
== text_it
->second
)
1824 return text_it
->first
;
1828 return UNKNOWN_TYPE
;
1831 views::Combobox
* AutofillDialogViews::ComboboxForType(
1832 ServerFieldType type
) {
1833 for (DetailGroupMap::iterator iter
= detail_groups_
.begin();
1834 iter
!= detail_groups_
.end(); ++iter
) {
1835 const DetailsGroup
& group
= iter
->second
;
1836 if (!delegate_
->SectionIsActive(group
.section
))
1839 ComboboxMap::const_iterator combo_mapping
= group
.comboboxes
.find(type
);
1840 if (combo_mapping
!= group
.comboboxes
.end())
1841 return combo_mapping
->second
;
1847 ServerFieldType
AutofillDialogViews::TypeForCombobox(
1848 const views::Combobox
* combobox
) const {
1849 for (DetailGroupMap::const_iterator it
= detail_groups_
.begin();
1850 it
!= detail_groups_
.end(); ++it
) {
1851 const DetailsGroup
& group
= it
->second
;
1852 if (!delegate_
->SectionIsActive(group
.section
))
1855 for (ComboboxMap::const_iterator combo_it
= group
.comboboxes
.begin();
1856 combo_it
!= group
.comboboxes
.end(); ++combo_it
) {
1857 if (combo_it
->second
== combobox
)
1858 return combo_it
->first
;
1862 return UNKNOWN_TYPE
;
1865 void AutofillDialogViews::DetailsContainerBoundsChanged() {
1867 error_bubble_
->UpdatePosition();
1870 void AutofillDialogViews::SetIconsForSection(DialogSection section
) {
1871 FieldValueMap user_input
;
1872 GetUserInput(section
, &user_input
);
1873 FieldIconMap field_icons
= delegate_
->IconsForFields(user_input
);
1874 TextfieldMap
* textfields
= &GroupForSection(section
)->textfields
;
1875 for (TextfieldMap::const_iterator textfield_it
= textfields
->begin();
1876 textfield_it
!= textfields
->end();
1878 ServerFieldType field_type
= textfield_it
->first
;
1879 FieldIconMap::const_iterator field_icon_it
= field_icons
.find(field_type
);
1880 ExpandingTextfield
* textfield
= textfield_it
->second
;
1881 if (field_icon_it
!= field_icons
.end())
1882 textfield
->SetIcon(field_icon_it
->second
);
1884 textfield
->SetTooltipIcon(delegate_
->TooltipForField(field_type
));
1888 void AutofillDialogViews::NonClientMousePressed() {
1889 delegate_
->FocusMoved();
1892 views::View
* AutofillDialogViews::GetNotificationAreaForTesting() {
1893 return notification_area_
;
1896 views::View
* AutofillDialogViews::GetScrollableAreaForTesting() {
1897 return scrollable_area_
;
1900 AutofillDialogViews::DetailsGroup::DetailsGroup(DialogSection section
)
1904 suggested_info(NULL
),
1905 suggested_button(NULL
) {}
1907 AutofillDialogViews::DetailsGroup::~DetailsGroup() {}
1909 } // namespace autofill