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_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
;
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
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
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
,
130 const SkColor
& fill_color
,
131 const SkColor
& stroke_color
) {
132 const int arrow_half_width
= kArrowWidth
/ 2.0f
;
135 arrow
.moveTo(tip_x
- arrow_half_width
, kArrowHeight
);
136 arrow
.lineTo(tip_x
, 0);
137 arrow
.lineTo(tip_x
+ arrow_half_width
, kArrowHeight
);
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
{
163 SectionRowView() { SetBorder(views::Border::CreateEmptyBorder(10, 0, 0, 0)); }
165 virtual ~SectionRowView() {}
167 // views::View implementation:
168 virtual gfx::Size
GetPreferredSize() const OVERRIDE
{
171 for (int i
= 0; i
< child_count(); ++i
) {
172 if (child_at(i
)->visible()) {
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();
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();
219 DISALLOW_COPY_AND_ASSIGN(SectionRowView
);
222 // A view that propagates visibility and preferred size changes.
223 class LayoutPropagationView
: public views::View
{
225 LayoutPropagationView() {}
226 virtual ~LayoutPropagationView() {}
229 virtual void ChildVisibilityChanged(views::View
* child
) OVERRIDE
{
230 PreferredSizeChanged();
232 virtual void ChildPreferredSizeChanged(views::View
* child
) OVERRIDE
{
233 PreferredSizeChanged();
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
{
245 NotificationView(const DialogNotification
& data
,
246 AutofillDialogViewDelegate
* delegate
)
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());
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()),
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(
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()));
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() {
311 // views::View implementation.
312 virtual gfx::Insets
GetInsets() const OVERRIDE
{
313 int vertical_padding
= kNotificationPadding
;
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() +
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(
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
)
364 delegate_
->LinkClicked(data_
.link_url());
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
{
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
{
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());
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
452 views::View
* GetAncestralInputView(views::View
* view
) {
453 if (view
->GetClassName() == views::Combobox::kViewClassName
)
456 return view
->GetAncestorWithClassName(ExpandingTextfield::kViewClassName
);
459 // A class that informs |delegate_| when an unhandled mouse press occurs.
460 class MousePressedHandler
: public ui::EventHandler
{
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();
472 AutofillDialogViewDelegate
* const delegate_
;
474 DISALLOW_COPY_AND_ASSIGN(MousePressedHandler
);
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));
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);
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(
528 const gfx::Point
& point
) {
529 DCHECK_EQ(menu_button_
, source
);
531 ui::MenuModel
* model
= delegate_
->MenuModelForAccountChooser();
535 menu_runner_
.reset(new views::MenuRunner(model
));
536 if (menu_runner_
->RunMenuAt(source
->GetWidget(),
538 source
->GetBoundsInScreen(),
539 views::MENU_ANCHOR_TOPRIGHT
,
540 ui::MENU_SOURCE_NONE
,
541 0) == views::MenuRunner::MENU_DELETED
) {
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
,
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())
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()) {
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
,
613 kOverlayMessageVerticalPadding
,
614 kDialogEdgePadding
));
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
);
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
;
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);
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).
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();
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
);
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())
747 for (size_t i
= 0; i
< notifications_
.size(); ++i
) {
748 const DialogNotification
& notification
= notifications_
[i
];
749 scoped_ptr
<NotificationView
> view(new NotificationView(notification
,
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.
765 const char* AutofillDialogViews::NotificationArea::GetClassName() const {
766 return kNotificationAreaClassName
;
769 void AutofillDialogViews::NotificationArea::PaintChildren(
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());
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
)
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());
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
,
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
);
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())
870 set_background(is_active
?
871 views::Background::CreateSolidBackground(kShadingColor
) :
876 void AutofillDialogViews::SectionContainer::SetForwardMouseEvents(
878 forward_mouse_events_
= 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
))
898 proxy_button_
->OnMouseEntered(ProxyEvent(event
));
902 void AutofillDialogViews::SectionContainer::OnMouseExited(
903 const ui::MouseEvent
& event
) {
905 if (!ShouldForwardEvent(event
))
908 proxy_button_
->OnMouseExited(ProxyEvent(event
));
912 bool AutofillDialogViews::SectionContainer::OnMousePressed(
913 const ui::MouseEvent
& event
) {
914 if (!ShouldForwardEvent(event
))
917 return proxy_button_
->OnMousePressed(ProxyEvent(event
));
920 void AutofillDialogViews::SectionContainer::OnMouseReleased(
921 const ui::MouseEvent
& event
) {
922 if (!ShouldForwardEvent(event
))
925 proxy_button_
->OnMouseReleased(ProxyEvent(event
));
928 void AutofillDialogViews::SectionContainer::OnGestureEvent(
929 ui::GestureEvent
* event
) {
930 if (!ShouldForwardEvent(*event
))
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
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.
945 rect
.CenterPoint().y() > child_at(0)->bounds().bottom()) {
949 // Special case for (CVC) inputs in the suggestion view.
950 if (forward_mouse_events_
&&
951 handler
->GetAncestorWithClassName(ExpandingTextfield::kViewClassName
)) {
955 // Special case for the proxy button itself.
956 if (handler
== proxy_button_
)
963 ui::MouseEvent
AutofillDialogViews::SectionContainer::ProxyEvent(
964 const ui::MouseEvent
& event
) {
965 ui::MouseEvent event_copy
= event
;
966 event_copy
.set_location(gfx::Point());
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
,
984 kMenuButtonBottomInset
,
986 gfx::Insets insets
= GetInsets();
987 insets
+= gfx::Insets(-kFocusBorderWidth
, -kFocusBorderWidth
,
988 -kFocusBorderWidth
, -kFocusBorderWidth
);
990 views::Painter::CreateDashedFocusPainterWithInsets(insets
));
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());
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()),
1060 new ExpandingTextfield(base::string16(),
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
);
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().
1097 int AutofillDialogViews::SuggestionView::GetHeightForWidth(int width
) const {
1099 CanUseVerticallyCompactText(width
, &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(
1124 layout
->GetPreferredHeightForWidth(&sizing_view
, available_width
));
1126 sizing_view
.SetLabelText(state_
.horizontally_compact_text
);
1127 calculated_heights_
[available_width
] = std::make_pair(
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
) {
1143 void AutofillDialogViews::SuggestionView::SetState(
1144 const SuggestionState
& state
) {
1145 calculated_heights_
.clear();
1147 SetVisible(state_
.visible
);
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);
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() {
1185 SetLabelText(CanUseVerticallyCompactText(width(), &unused
) ?
1186 state_
.vertically_compact_text
:
1187 state_
.horizontally_compact_text
);
1190 // AutofillDialogView ----------------------------------------------------------
1193 AutofillDialogView
* AutofillDialogView::Create(
1194 AutofillDialogViewDelegate
* delegate
) {
1195 return new AutofillDialogViews(delegate
);
1198 // AutofillDialogViews ---------------------------------------------------------
1200 AutofillDialogViews::AutofillDialogViews(AutofillDialogViewDelegate
* delegate
)
1201 : delegate_(delegate
),
1203 needs_update_(false),
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
),
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() {
1238 void AutofillDialogViews::Show() {
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() {
1278 void AutofillDialogViews::UpdatesStarted() {
1282 void AutofillDialogViews::UpdatesFinished() {
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()) {
1297 loading_shield_height_
= std::max(kInitialLoadingShieldHeight
,
1298 GetContentsBounds().height());
1299 ShowDialogInMode(LOADING
);
1301 bool show_sign_in
= delegate_
->ShouldShowSignInWebView();
1302 ShowDialogInMode(show_sign_in
? SIGN_IN
: DETAIL_INPUT
);
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();
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() {
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())
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(
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();
1449 ShowDialogInMode(DETAIL_INPUT
);
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
);
1493 if (loading_shield_
->visible()) {
1494 loading_shield_
->SetBoundsRect(bounds());
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()) {
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);
1522 error_bubble_
->UpdatePosition();
1525 void AutofillDialogViews::OnNativeThemeChanged(
1526 const ui::NativeTheme
* theme
) {
1527 if (!legal_document_view_
)
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
1544 return title
.empty() ? base::ASCIIToUTF16(" ") : title
;
1547 void AutofillDialogViews::WindowClosing() {
1548 focus_manager_
->RemoveFocusChangeListener(this);
1551 void AutofillDialogViews::DeleteDelegate() {
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 {
1578 bool AutofillDialogViews::IsDialogButtonEnabled(ui::DialogButton button
) const {
1579 return delegate_
->IsDialogButtonEnabled(button
);
1582 views::View
* AutofillDialogViews::GetInitiallyFocusedView() {
1583 if (!window_
|| !focus_manager_
)
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_
;
1597 next
= manager
->GetNextFocusableView(next
, window_
, false, true)) {
1598 views::View
* input_view
= GetAncestralInputView(next
);
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()) {
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
,
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() {
1651 return delegate_
->OnAccept();
1653 // |ValidateForm()| failed; there should be invalid views in |validity_map_|.
1654 DCHECK(!validity_map_
.empty());
1660 void AutofillDialogViews::ContentsChanged(views::Textfield
* sender
,
1661 const base::string16
& new_contents
) {
1662 InputEditedOrActivated(TypeForTextfield(sender
),
1663 sender
->GetBoundsInScreen(),
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(©
);
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(),
1685 // Show an error bubble if a user clicks on an input that's already focused
1687 ShowErrorBubbleForViewIfNecessary(sender
);
1693 void AutofillDialogViews::OnWillChangeFocus(
1694 views::View
* focused_before
,
1695 views::View
* focused_now
) {
1696 delegate_
->FocusMoved();
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.
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
,
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
;
1744 if (!group
->suggested_button
->visible())
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(),
1759 views::MENU_ANCHOR_TOPRIGHT
,
1760 ui::MENU_SOURCE_NONE
,
1761 0) == views::MenuRunner::MENU_DELETED
) {
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_
)->
1779 return gfx::Size(width
+ insets
.width(), size
.height() + insets
.height());
1782 if (overlay_view_
->visible()) {
1783 const int height
= overlay_view_
->GetHeightForContentsForWidth(width
);
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());
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
))
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
),
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);
1929 // TODO(estade): we should be using Chrome-style constrained window padding
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
;
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
);
1963 ExpandingTextfield
* field
= new ExpandingTextfield(input
.initial_value
,
1964 input
.placeholder_text
,
1965 input
.IsMultiline(),
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());
1976 if (input
.length
== DetailInput::LONG
)
1979 views::ColumnSet
* column_set
= layout
->GetColumnSet(column_set_id
);
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
);
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
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
2005 layout
->AddView(view_to_add
.release(), 1, 1,
2006 views::GridLayout::FILL
, views::GridLayout::FILL
,
2009 if (input
.length
== DetailInput::LONG
||
2010 input
.length
== DetailInput::SHORT_EOL
) {
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
);
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
);
2049 to_focus
->RequestFocus();
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();
2112 void AutofillDialogViews::SetValidityForInput(
2114 const base::string16
& message
) {
2115 bool invalid
= !message
.empty();
2116 input
->SetInvalid(invalid
);
2119 validity_map_
[input
] = message
;
2121 validity_map_
.erase(input
);
2123 if (error_bubble_
&&
2124 error_bubble_
->anchor()->GetAncestorWithClassName(
2125 input
->GetClassName()) == input
) {
2126 validity_map_
.erase(input
);
2132 void AutofillDialogViews::ShowErrorBubbleForViewIfNecessary(views::View
* view
) {
2133 if (!view
->GetWidget())
2136 if (!delegate_
->ShouldShowErrorBubble()) {
2137 DCHECK(!error_bubble_
);
2141 if (view
->GetClassName() == DecoratedTextfield::kViewClassName
&&
2142 !static_cast<DecoratedTextfield
*>(view
)->invalid()) {
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
) {
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() {
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
);
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())
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())
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();
2234 detail_outputs
[CREDIT_CARD_VERIFICATION_CODE
] = cvc
->GetText();
2237 ValidityMessages validity
= delegate_
->InputsAreValid(group
.section
,
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())
2255 if (!ValidateGroup(group
, VALIDATE_FINAL
))
2262 void AutofillDialogViews::InputEditedOrActivated(ServerFieldType type
,
2263 const gfx::Rect
& bounds
,
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
)
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());
2283 delegate_
->UserEditedOrActivatedInput(group
->section
,
2285 GetWidget()->GetNativeView(),
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(
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;
2328 preferred_size_
= gfx::Size();
2330 if (GetWidget() && delegate_
&& delegate_
->GetWebContents()) {
2331 UpdateWebContentsModalDialogPosition(
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
) {
2349 views::View
* input_view
= GetAncestralInputView(view
);
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
)
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()) {
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
++);
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
))
2394 TextfieldMap::const_iterator text_mapping
= group
.textfields
.find(type
);
2395 if (text_mapping
!= group
.textfields
.end())
2396 return text_mapping
->second
;
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
))
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
))
2434 ComboboxMap::const_iterator combo_mapping
= group
.comboboxes
.find(type
);
2435 if (combo_mapping
!= group
.comboboxes
.end())
2436 return combo_mapping
->second
;
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
))
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() {
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();
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
);
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
);
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
)
2516 suggested_info(NULL
),
2517 suggested_button(NULL
) {}
2519 AutofillDialogViews::DetailsGroup::~DetailsGroup() {}
2521 } // namespace autofill