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/decorated_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 "content/public/browser/web_contents_view.h"
29 #include "grit/theme_resources.h"
30 #include "grit/ui_resources.h"
31 #include "third_party/skia/include/core/SkColor.h"
32 #include "ui/base/l10n/l10n_util.h"
33 #include "ui/base/models/combobox_model.h"
34 #include "ui/base/models/menu_model.h"
35 #include "ui/base/resource/resource_bundle.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"
69 using web_modal::WebContentsModalDialogManager
;
70 using web_modal::WebContentsModalDialogManagerDelegate
;
76 // The width for the section container.
77 const int kSectionContainerWidth
= 440;
79 // The minimum useful height of the contents area of the dialog.
80 const int kMinimumContentsHeight
= 101;
82 // The default height of the loading shield, also its minimum size.
83 const int kInitialLoadingShieldHeight
= 150;
85 // Horizontal padding between text and other elements (in pixels).
86 const int kAroundTextPadding
= 4;
88 // The space between the edges of a notification bar and the text within (in
90 const int kNotificationPadding
= 17;
92 // Vertical padding above and below each detail section (in pixels).
93 const int kDetailSectionVerticalPadding
= 10;
95 const int kArrowHeight
= 7;
96 const int kArrowWidth
= 2 * kArrowHeight
;
98 // The padding inside the edges of the dialog, in pixels.
99 const int kDialogEdgePadding
= 20;
101 // The vertical padding between rows of manual inputs (in pixels).
102 const int kManualInputRowPadding
= 10;
104 // Slight shading for mouse hover and legal document background.
105 SkColor kShadingColor
= SkColorSetARGB(7, 0, 0, 0);
107 // A border color for the legal document view.
108 SkColor kSubtleBorderColor
= SkColorSetARGB(10, 0, 0, 0);
110 // The top and bottom padding, in pixels, for the suggestions menu dropdown
112 const int kMenuButtonTopInset
= 3;
113 const int kMenuButtonBottomInset
= 6;
115 // The height in pixels of the padding above and below the overlay message view.
116 const int kOverlayMessageVerticalPadding
= 34;
118 // Spacing below image and above text messages in overlay view.
119 const int kOverlayImageBottomMargin
= 100;
121 const char kNotificationAreaClassName
[] = "autofill/NotificationArea";
122 const char kOverlayViewClassName
[] = "autofill/OverlayView";
123 const char kSectionContainerClassName
[] = "autofill/SectionContainer";
124 const char kSuggestedButtonClassName
[] = "autofill/SuggestedButton";
126 // Draws an arrow at the top of |canvas| pointing to |tip_x|.
127 void DrawArrow(gfx::Canvas
* canvas
,
129 const SkColor
& fill_color
,
130 const SkColor
& stroke_color
) {
131 const int arrow_half_width
= kArrowWidth
/ 2.0f
;
134 arrow
.moveTo(tip_x
- arrow_half_width
, kArrowHeight
);
135 arrow
.lineTo(tip_x
, 0);
136 arrow
.lineTo(tip_x
+ arrow_half_width
, kArrowHeight
);
139 fill_paint
.setColor(fill_color
);
140 canvas
->DrawPath(arrow
, fill_paint
);
142 if (stroke_color
!= SK_ColorTRANSPARENT
) {
143 SkPaint stroke_paint
;
144 stroke_paint
.setColor(stroke_color
);
145 stroke_paint
.setStyle(SkPaint::kStroke_Style
);
146 canvas
->DrawPath(arrow
, stroke_paint
);
150 // Returns whether |view| is an input (e.g. textfield, combobox).
151 bool IsInput(views::View
* view
) {
152 return view
->GetClassName() == DecoratedTextfield::kViewClassName
||
153 view
->GetClassName() == views::Combobox::kViewClassName
;
156 void SelectComboboxValueOrSetToDefault(views::Combobox
* combobox
,
157 const base::string16
& value
) {
158 if (!combobox
->SelectValue(value
))
159 combobox
->SetSelectedIndex(combobox
->model()->GetDefaultIndex());
162 // This class handles layout for the first row of a SuggestionView.
163 // It exists to circumvent shortcomings of GridLayout and BoxLayout (namely that
164 // the former doesn't fully respect child visibility, and that the latter won't
165 // expand a single child).
166 class SectionRowView
: public views::View
{
168 SectionRowView() { SetBorder(views::Border::CreateEmptyBorder(10, 0, 0, 0)); }
170 virtual ~SectionRowView() {}
172 // views::View implementation:
173 virtual gfx::Size
GetPreferredSize() OVERRIDE
{
176 for (int i
= 0; i
< child_count(); ++i
) {
177 if (child_at(i
)->visible()) {
179 width
+= kAroundTextPadding
;
181 gfx::Size size
= child_at(i
)->GetPreferredSize();
182 height
= std::max(height
, size
.height());
183 width
+= size
.width();
187 gfx::Insets insets
= GetInsets();
188 return gfx::Size(width
+ insets
.width(), height
+ insets
.height());
191 virtual void Layout() OVERRIDE
{
192 const gfx::Rect bounds
= GetContentsBounds();
194 // Icon is left aligned.
195 int start_x
= bounds
.x();
196 views::View
* icon
= child_at(0);
197 if (icon
->visible()) {
198 icon
->SizeToPreferredSize();
200 icon
->SetY(bounds
.y() +
201 (bounds
.height() - icon
->bounds().height()) / 2);
202 start_x
+= icon
->bounds().width() + kAroundTextPadding
;
205 // Textfield is right aligned.
206 int end_x
= bounds
.width();
207 views::View
* decorated
= child_at(2);
208 if (decorated
->visible()) {
209 const int preferred_width
= decorated
->GetPreferredSize().width();
210 decorated
->SetBounds(bounds
.width() - preferred_width
, bounds
.y(),
211 preferred_width
, bounds
.height());
212 end_x
= decorated
->bounds().x() - kAroundTextPadding
;
215 // Label takes up all the space in between.
216 views::View
* label
= child_at(1);
217 if (label
->visible())
218 label
->SetBounds(start_x
, bounds
.y(), end_x
- start_x
, bounds
.height());
220 views::View::Layout();
224 DISALLOW_COPY_AND_ASSIGN(SectionRowView
);
227 // A view that propagates visibility and preferred size changes.
228 class LayoutPropagationView
: public views::View
{
230 LayoutPropagationView() {}
231 virtual ~LayoutPropagationView() {}
234 virtual void ChildVisibilityChanged(views::View
* child
) OVERRIDE
{
235 PreferredSizeChanged();
237 virtual void ChildPreferredSizeChanged(views::View
* child
) OVERRIDE
{
238 PreferredSizeChanged();
242 DISALLOW_COPY_AND_ASSIGN(LayoutPropagationView
);
245 // A View for a single notification banner.
246 class NotificationView
: public views::View
,
247 public views::ButtonListener
,
248 public views::StyledLabelListener
{
250 NotificationView(const DialogNotification
& data
,
251 AutofillDialogViewDelegate
* delegate
)
255 scoped_ptr
<views::View
> label_view
;
256 if (data
.HasCheckbox()) {
257 scoped_ptr
<views::Checkbox
> checkbox(
258 new views::Checkbox(base::string16()));
259 checkbox
->SetText(data
.display_text());
260 checkbox
->SetTextMultiLine(true);
261 checkbox
->SetHorizontalAlignment(gfx::ALIGN_LEFT
);
262 checkbox
->SetTextColor(views::Button::STATE_NORMAL
,
263 data
.GetTextColor());
264 checkbox
->SetTextColor(views::Button::STATE_HOVERED
,
265 data
.GetTextColor());
266 checkbox
->SetChecked(data
.checked());
267 checkbox
->set_listener(this);
268 checkbox_
= checkbox
.get();
269 label_view
.reset(checkbox
.release());
271 scoped_ptr
<views::StyledLabel
> label(new views::StyledLabel(
272 data
.display_text(), this));
273 label
->set_auto_color_readability_enabled(false);
275 views::StyledLabel::RangeStyleInfo text_style
;
276 text_style
.color
= data
.GetTextColor();
278 if (data
.link_range().is_empty()) {
279 label
->AddStyleRange(gfx::Range(0, data
.display_text().size()),
282 gfx::Range
prefix_range(0, data
.link_range().start());
283 if (!prefix_range
.is_empty())
284 label
->AddStyleRange(prefix_range
, text_style
);
286 label
->AddStyleRange(
288 views::StyledLabel::RangeStyleInfo::CreateForLink());
290 gfx::Range
suffix_range(data
.link_range().end(),
291 data
.display_text().size());
292 if (!suffix_range
.is_empty())
293 label
->AddStyleRange(suffix_range
, text_style
);
296 label_view
.reset(label
.release());
299 AddChildView(label_view
.release());
301 if (!data
.tooltip_text().empty())
302 AddChildView(new TooltipIcon(data
.tooltip_text()));
305 views::Background::CreateSolidBackground(data
.GetBackgroundColor()));
306 SetBorder(views::Border::CreateSolidSidedBorder(
307 1, 0, 1, 0, data
.GetBorderColor()));
310 virtual ~NotificationView() {}
312 views::Checkbox
* checkbox() {
316 // views::View implementation.
317 virtual gfx::Insets
GetInsets() const OVERRIDE
{
318 int vertical_padding
= kNotificationPadding
;
320 vertical_padding
-= 3;
321 return gfx::Insets(vertical_padding
, kDialogEdgePadding
,
322 vertical_padding
, kDialogEdgePadding
);
325 virtual int GetHeightForWidth(int width
) OVERRIDE
{
326 int label_width
= width
- GetInsets().width();
327 if (child_count() > 1) {
328 views::View
* tooltip_icon
= child_at(1);
329 label_width
-= tooltip_icon
->GetPreferredSize().width() +
333 return child_at(0)->GetHeightForWidth(label_width
) + GetInsets().height();
336 virtual void Layout() OVERRIDE
{
337 // Surprisingly, GetContentsBounds() doesn't consult GetInsets().
338 gfx::Rect bounds
= GetLocalBounds();
339 bounds
.Inset(GetInsets());
340 int right_bound
= bounds
.right();
342 if (child_count() > 1) {
343 // The icon takes up the entire vertical space and an extra 20px on
344 // each side. This increases the hover target for the tooltip.
345 views::View
* tooltip_icon
= child_at(1);
346 gfx::Size icon_size
= tooltip_icon
->GetPreferredSize();
347 int icon_width
= icon_size
.width() + kDialogEdgePadding
;
348 right_bound
-= icon_width
;
349 tooltip_icon
->SetBounds(
351 icon_width
+ kDialogEdgePadding
, GetLocalBounds().height());
354 child_at(0)->SetBounds(bounds
.x(), bounds
.y(),
355 right_bound
- bounds
.x(), bounds
.height());
358 // views::ButtonListener implementation.
359 virtual void ButtonPressed(views::Button
* sender
,
360 const ui::Event
& event
) OVERRIDE
{
361 DCHECK_EQ(sender
, checkbox_
);
362 delegate_
->NotificationCheckboxStateChanged(data_
.type(),
363 checkbox_
->checked());
366 // views::StyledLabelListener implementation.
367 virtual void StyledLabelLinkClicked(const gfx::Range
& range
, int event_flags
)
369 delegate_
->LinkClicked(data_
.link_url());
373 // The model data for this notification.
374 DialogNotification data_
;
376 // The delegate that handles interaction with |this|.
377 AutofillDialogViewDelegate
* delegate_
;
379 // The checkbox associated with this notification, or NULL if there is none.
380 views::Checkbox
* checkbox_
;
382 DISALLOW_COPY_AND_ASSIGN(NotificationView
);
385 // A view that displays a loading message with some dancing dots.
386 class LoadingAnimationView
: public views::View
,
387 public gfx::AnimationDelegate
{
389 explicit LoadingAnimationView(const base::string16
& text
) :
390 container_(new views::View()) {
391 AddChildView(container_
);
392 container_
->SetLayoutManager(
393 new views::BoxLayout(views::BoxLayout::kHorizontal
, 0, 0, 0));
395 const gfx::FontList
& font_list
=
396 ui::ResourceBundle::GetSharedInstance().GetFontList(
397 ui::ResourceBundle::LargeFont
);
398 animation_
.reset(new LoadingAnimation(this, font_list
.GetHeight()));
400 container_
->AddChildView(new views::Label(text
, font_list
));
402 for (size_t i
= 0; i
< 3; ++i
) {
403 container_
->AddChildView(
404 new views::Label(base::ASCIIToUTF16("."), font_list
));
407 OnNativeThemeChanged(GetNativeTheme());
410 virtual ~LoadingAnimationView() {}
412 // views::View implementation.
413 virtual void SetVisible(bool visible
) OVERRIDE
{
419 views::View::SetVisible(visible
);
422 virtual void Layout() OVERRIDE
{
423 gfx::Size container_size
= container_
->GetPreferredSize();
424 gfx::Rect
container_bounds((width() - container_size
.width()) / 2,
425 (height() - container_size
.height()) / 2,
426 container_size
.width(),
427 container_size
.height());
428 container_
->SetBoundsRect(container_bounds
);
429 container_
->Layout();
431 for (size_t i
= 0; i
< 3; ++i
) {
432 views::View
* dot
= container_
->child_at(i
+ 1);
433 dot
->SetY(dot
->y() + animation_
->GetCurrentValueForDot(i
));
437 virtual void OnNativeThemeChanged(const ui::NativeTheme
* theme
) OVERRIDE
{
438 set_background(views::Background::CreateSolidBackground(
439 theme
->GetSystemColor(ui::NativeTheme::kColorId_DialogBackground
)));
442 // gfx::AnimationDelegate implementation.
443 virtual void AnimationProgressed(const gfx::Animation
* animation
) OVERRIDE
{
444 DCHECK_EQ(animation
, animation_
.get());
449 // Contains the "Loading" label and the dots.
450 views::View
* container_
;
452 scoped_ptr
<LoadingAnimation
> animation_
;
454 DISALLOW_COPY_AND_ASSIGN(LoadingAnimationView
);
459 // AutofillDialogViews::AccountChooser -----------------------------------------
461 AutofillDialogViews::AccountChooser::AccountChooser(
462 AutofillDialogViewDelegate
* delegate
)
463 : image_(new views::ImageView()),
464 menu_button_(new views::MenuButton(NULL
, base::string16(), this, true)),
465 link_(new views::Link()),
466 delegate_(delegate
) {
467 SetBorder(views::Border::CreateEmptyBorder(0, 0, 0, 10));
469 new views::BoxLayout(views::BoxLayout::kHorizontal
, 0, 0,
470 kAroundTextPadding
));
471 AddChildView(image_
);
473 menu_button_
->set_background(NULL
);
474 menu_button_
->SetBorder(views::Border::NullBorder());
475 gfx::Insets insets
= GetInsets();
476 menu_button_
->SetFocusPainter(
477 views::Painter::CreateDashedFocusPainterWithInsets(insets
));
478 menu_button_
->SetFocusable(true);
479 AddChildView(menu_button_
);
481 link_
->set_listener(this);
485 AutofillDialogViews::AccountChooser::~AccountChooser() {}
487 void AutofillDialogViews::AccountChooser::Update() {
488 SetVisible(delegate_
->ShouldShowAccountChooser());
490 gfx::Image icon
= delegate_
->AccountChooserImage();
491 image_
->SetImage(icon
.AsImageSkia());
492 menu_button_
->SetText(delegate_
->AccountChooserText());
493 // This allows the button to shrink if the new text is smaller.
494 menu_button_
->ClearMaxTextSize();
496 bool show_link
= !delegate_
->MenuModelForAccountChooser();
497 menu_button_
->SetVisible(!show_link
);
498 link_
->SetText(delegate_
->SignInLinkText());
499 link_
->SetVisible(show_link
);
501 menu_runner_
.reset();
503 PreferredSizeChanged();
506 void AutofillDialogViews::AccountChooser::OnMenuButtonClicked(
508 const gfx::Point
& point
) {
509 DCHECK_EQ(menu_button_
, source
);
511 ui::MenuModel
* model
= delegate_
->MenuModelForAccountChooser();
515 menu_runner_
.reset(new views::MenuRunner(model
));
516 if (menu_runner_
->RunMenuAt(source
->GetWidget(),
518 source
->GetBoundsInScreen(),
519 views::MenuItemView::TOPRIGHT
,
520 ui::MENU_SOURCE_NONE
,
521 0) == views::MenuRunner::MENU_DELETED
) {
526 views::View
* AutofillDialogViews::GetLoadingShieldForTesting() {
527 return loading_shield_
;
530 views::WebView
* AutofillDialogViews::GetSignInWebViewForTesting() {
531 return sign_in_web_view_
;
534 views::View
* AutofillDialogViews::GetNotificationAreaForTesting() {
535 return notification_area_
;
538 views::View
* AutofillDialogViews::GetScrollableAreaForTesting() {
539 return scrollable_area_
;
542 void AutofillDialogViews::AccountChooser::LinkClicked(views::Link
* source
,
544 delegate_
->SignInLinkClicked();
547 // AutofillDialogViews::OverlayView --------------------------------------------
549 AutofillDialogViews::OverlayView::OverlayView(
550 AutofillDialogViewDelegate
* delegate
)
551 : delegate_(delegate
),
552 image_view_(new views::ImageView()),
553 message_view_(new views::Label()) {
554 message_view_
->SetAutoColorReadabilityEnabled(false);
555 message_view_
->SetMultiLine(true);
557 AddChildView(image_view_
);
558 AddChildView(message_view_
);
560 OnNativeThemeChanged(GetNativeTheme());
563 AutofillDialogViews::OverlayView::~OverlayView() {}
565 int AutofillDialogViews::OverlayView::GetHeightForContentsForWidth(int width
) {
566 // In this case, 0 means "no preference".
567 if (!message_view_
->visible())
570 return kOverlayImageBottomMargin
+
571 views::kButtonVEdgeMarginNew
+
572 message_view_
->GetHeightForWidth(width
) +
573 image_view_
->GetHeightForWidth(width
);
576 void AutofillDialogViews::OverlayView::UpdateState() {
577 const DialogOverlayState
& state
= delegate_
->GetDialogOverlay();
579 if (state
.image
.IsEmpty()) {
584 image_view_
->SetImage(state
.image
.ToImageSkia());
586 message_view_
->SetVisible(!state
.string
.text
.empty());
587 message_view_
->SetText(state
.string
.text
);
588 message_view_
->SetFontList(state
.string
.font_list
);
589 message_view_
->SetEnabledColor(GetNativeTheme()->GetSystemColor(
590 ui::NativeTheme::kColorId_TextfieldReadOnlyColor
));
592 message_view_
->SetBorder(
593 views::Border::CreateEmptyBorder(kOverlayMessageVerticalPadding
,
595 kOverlayMessageVerticalPadding
,
596 kDialogEdgePadding
));
601 gfx::Insets
AutofillDialogViews::OverlayView::GetInsets() const {
602 return gfx::Insets(12, 12, 12, 12);
605 void AutofillDialogViews::OverlayView::Layout() {
606 gfx::Rect bounds
= ContentBoundsSansBubbleBorder();
607 if (!message_view_
->visible()) {
608 image_view_
->SetBoundsRect(bounds
);
612 int message_height
= message_view_
->GetHeightForWidth(bounds
.width());
613 int y
= bounds
.bottom() - message_height
;
614 message_view_
->SetBounds(bounds
.x(), y
, bounds
.width(), message_height
);
616 gfx::Size image_size
= image_view_
->GetPreferredSize();
617 y
-= image_size
.height() + kOverlayImageBottomMargin
;
618 image_view_
->SetBounds(bounds
.x(), y
, bounds
.width(), image_size
.height());
621 const char* AutofillDialogViews::OverlayView::GetClassName() const {
622 return kOverlayViewClassName
;
625 void AutofillDialogViews::OverlayView::OnPaint(gfx::Canvas
* canvas
) {
626 // BubbleFrameView doesn't mask the window, it just draws the border via
627 // image assets. Match that rounding here.
628 gfx::Rect rect
= ContentBoundsSansBubbleBorder();
629 const SkScalar kCornerRadius
= SkIntToScalar(
630 GetBubbleBorder() ? GetBubbleBorder()->GetBorderCornerRadius() : 2);
631 gfx::Path window_mask
;
632 window_mask
.addRoundRect(gfx::RectToSkRect(rect
),
633 kCornerRadius
, kCornerRadius
);
634 canvas
->ClipPath(window_mask
);
636 OnPaintBackground(canvas
);
638 // Draw the arrow, border, and fill for the bottom area.
639 if (message_view_
->visible()) {
640 const int arrow_half_width
= kArrowWidth
/ 2.0f
;
642 int y
= message_view_
->y() - 1;
643 // Note that we purposely draw slightly outside of |rect| so that the
644 // stroke is hidden on the sides.
645 arrow
.moveTo(rect
.x() - 1, y
);
646 arrow
.rLineTo(rect
.width() / 2 - arrow_half_width
, 0);
647 arrow
.rLineTo(arrow_half_width
, -kArrowHeight
);
648 arrow
.rLineTo(arrow_half_width
, kArrowHeight
);
649 arrow
.lineTo(rect
.right() + 1, y
);
650 arrow
.lineTo(rect
.right() + 1, rect
.bottom() + 1);
651 arrow
.lineTo(rect
.x() - 1, rect
.bottom() + 1);
654 // The mocked alpha blends were 7 for background & 10 for the border against
655 // a very bright background. The eye perceives luminance differences of
656 // darker colors much less than lighter colors, so increase the alpha blend
657 // amount the darker the color (lower the luminance).
659 SkColor background_color
= background()->get_color();
660 int background_luminance
=
661 color_utils::GetLuminanceForColor(background_color
);
662 int background_alpha
= static_cast<int>(
663 7 + 15 * (255 - background_luminance
) / 255);
664 int subtle_border_alpha
= static_cast<int>(
665 10 + 20 * (255 - background_luminance
) / 255);
667 paint
.setColor(color_utils::BlendTowardOppositeLuminance(
668 background_color
, background_alpha
));
669 paint
.setStyle(SkPaint::kFill_Style
);
670 canvas
->DrawPath(arrow
, paint
);
671 paint
.setColor(color_utils::BlendTowardOppositeLuminance(
672 background_color
, subtle_border_alpha
));
673 paint
.setStyle(SkPaint::kStroke_Style
);
674 canvas
->DrawPath(arrow
, paint
);
677 PaintChildren(canvas
);
680 void AutofillDialogViews::OverlayView::OnNativeThemeChanged(
681 const ui::NativeTheme
* theme
) {
682 set_background(views::Background::CreateSolidBackground(
683 theme
->GetSystemColor(ui::NativeTheme::kColorId_DialogBackground
)));
686 views::BubbleBorder
* AutofillDialogViews::OverlayView::GetBubbleBorder() {
687 views::View
* frame
= GetWidget()->non_client_view()->frame_view();
688 std::string
bubble_frame_view_name(views::BubbleFrameView::kViewClassName
);
689 if (frame
->GetClassName() == bubble_frame_view_name
)
690 return static_cast<views::BubbleFrameView
*>(frame
)->bubble_border();
695 gfx::Rect
AutofillDialogViews::OverlayView::ContentBoundsSansBubbleBorder() {
696 gfx::Rect bounds
= GetContentsBounds();
697 int bubble_width
= 5;
698 if (GetBubbleBorder())
699 bubble_width
= GetBubbleBorder()->GetBorderThickness();
700 bounds
.Inset(bubble_width
, bubble_width
, bubble_width
, bubble_width
);
704 // AutofillDialogViews::NotificationArea ---------------------------------------
706 AutofillDialogViews::NotificationArea::NotificationArea(
707 AutofillDialogViewDelegate
* delegate
)
708 : delegate_(delegate
) {
709 // Reserve vertical space for the arrow (regardless of whether one exists).
710 // The -1 accounts for the border.
711 SetBorder(views::Border::CreateEmptyBorder(kArrowHeight
- 1, 0, 0, 0));
713 views::BoxLayout
* box_layout
=
714 new views::BoxLayout(views::BoxLayout::kVertical
, 0, 0, 0);
715 SetLayoutManager(box_layout
);
718 AutofillDialogViews::NotificationArea::~NotificationArea() {}
720 void AutofillDialogViews::NotificationArea::SetNotifications(
721 const std::vector
<DialogNotification
>& notifications
) {
722 notifications_
= notifications
;
724 RemoveAllChildViews(true);
726 if (notifications_
.empty())
729 for (size_t i
= 0; i
< notifications_
.size(); ++i
) {
730 const DialogNotification
& notification
= notifications_
[i
];
731 scoped_ptr
<NotificationView
> view(new NotificationView(notification
,
734 AddChildView(view
.release());
737 PreferredSizeChanged();
740 gfx::Size
AutofillDialogViews::NotificationArea::GetPreferredSize() {
741 gfx::Size size
= views::View::GetPreferredSize();
742 // Ensure that long notifications wrap and don't enlarge the dialog.
747 const char* AutofillDialogViews::NotificationArea::GetClassName() const {
748 return kNotificationAreaClassName
;
751 void AutofillDialogViews::NotificationArea::PaintChildren(
752 gfx::Canvas
* canvas
) {}
754 void AutofillDialogViews::NotificationArea::OnPaint(gfx::Canvas
* canvas
) {
755 views::View::OnPaint(canvas
);
756 views::View::PaintChildren(canvas
);
761 GetMirroredXInView(width() - arrow_centering_anchor_
->width() / 2.0f
),
762 notifications_
[0].GetBackgroundColor(),
763 notifications_
[0].GetBorderColor());
767 void AutofillDialogViews::OnWidgetClosing(views::Widget
* widget
) {
768 observer_
.Remove(widget
);
769 if (error_bubble_
&& error_bubble_
->GetWidget() == widget
)
770 error_bubble_
= NULL
;
773 void AutofillDialogViews::OnWidgetBoundsChanged(views::Widget
* widget
,
774 const gfx::Rect
& new_bounds
) {
775 // Notify the web contents of its new auto-resize limits.
776 if (sign_in_delegate_
&& sign_in_web_view_
->visible()) {
777 sign_in_delegate_
->UpdateLimitsAndEnableAutoResize(
778 GetMinimumSignInViewSize(), GetMaximumSignInViewSize());
783 bool AutofillDialogViews::NotificationArea::HasArrow() {
784 return !notifications_
.empty() && notifications_
[0].HasArrow() &&
785 arrow_centering_anchor_
.get();
788 // AutofillDialogViews::SectionContainer ---------------------------------------
790 AutofillDialogViews::SectionContainer::SectionContainer(
791 const base::string16
& label
,
792 views::View
* controls
,
793 views::Button
* proxy_button
)
794 : proxy_button_(proxy_button
),
795 forward_mouse_events_(false) {
796 set_notify_enter_exit_on_child(true);
798 SetBorder(views::Border::CreateEmptyBorder(kDetailSectionVerticalPadding
,
800 kDetailSectionVerticalPadding
,
801 kDialogEdgePadding
));
803 ui::ResourceBundle
& rb
= ui::ResourceBundle::GetSharedInstance();
804 views::Label
* label_view
= new views::Label(
805 label
, rb
.GetFontList(ui::ResourceBundle::BoldFont
));
806 label_view
->SetHorizontalAlignment(gfx::ALIGN_LEFT
);
808 views::View
* label_bar
= new views::View();
809 views::GridLayout
* label_bar_layout
= new views::GridLayout(label_bar
);
810 label_bar
->SetLayoutManager(label_bar_layout
);
811 const int kColumnSetId
= 0;
812 views::ColumnSet
* columns
= label_bar_layout
->AddColumnSet(kColumnSetId
);
814 views::GridLayout::LEADING
,
815 views::GridLayout::LEADING
,
817 views::GridLayout::FIXED
,
818 kSectionContainerWidth
- proxy_button
->GetPreferredSize().width(),
820 columns
->AddColumn(views::GridLayout::LEADING
,
821 views::GridLayout::LEADING
,
823 views::GridLayout::USE_PREF
,
826 label_bar_layout
->StartRow(0, kColumnSetId
);
827 label_bar_layout
->AddView(label_view
);
828 label_bar_layout
->AddView(proxy_button
);
830 SetLayoutManager(new views::BoxLayout(views::BoxLayout::kVertical
, 0, 0, 0));
831 AddChildView(label_bar
);
832 AddChildView(controls
);
835 AutofillDialogViews::SectionContainer::~SectionContainer() {}
837 void AutofillDialogViews::SectionContainer::SetActive(bool active
) {
838 bool is_active
= active
&& proxy_button_
->visible();
839 if (is_active
== !!background())
842 set_background(is_active
?
843 views::Background::CreateSolidBackground(kShadingColor
) :
848 void AutofillDialogViews::SectionContainer::SetForwardMouseEvents(
850 forward_mouse_events_
= forward
;
852 set_background(NULL
);
855 const char* AutofillDialogViews::SectionContainer::GetClassName() const {
856 return kSectionContainerClassName
;
859 void AutofillDialogViews::SectionContainer::OnMouseMoved(
860 const ui::MouseEvent
& event
) {
861 SetActive(ShouldForwardEvent(event
));
864 void AutofillDialogViews::SectionContainer::OnMouseEntered(
865 const ui::MouseEvent
& event
) {
866 if (!ShouldForwardEvent(event
))
870 proxy_button_
->OnMouseEntered(ProxyEvent(event
));
874 void AutofillDialogViews::SectionContainer::OnMouseExited(
875 const ui::MouseEvent
& event
) {
877 if (!ShouldForwardEvent(event
))
880 proxy_button_
->OnMouseExited(ProxyEvent(event
));
884 bool AutofillDialogViews::SectionContainer::OnMousePressed(
885 const ui::MouseEvent
& event
) {
886 if (!ShouldForwardEvent(event
))
889 return proxy_button_
->OnMousePressed(ProxyEvent(event
));
892 void AutofillDialogViews::SectionContainer::OnMouseReleased(
893 const ui::MouseEvent
& event
) {
894 if (!ShouldForwardEvent(event
))
897 proxy_button_
->OnMouseReleased(ProxyEvent(event
));
900 void AutofillDialogViews::SectionContainer::OnGestureEvent(
901 ui::GestureEvent
* event
) {
902 if (!ShouldForwardEvent(*event
))
905 proxy_button_
->OnGestureEvent(event
);
908 views::View
* AutofillDialogViews::SectionContainer::GetEventHandlerForRect(
909 const gfx::Rect
& rect
) {
910 // TODO(tdanderson): Modify this function to support rect-based event
913 views::View
* handler
= views::View::GetEventHandlerForRect(rect
);
914 // If the event is not in the label bar and there's no background to be
915 // cleared, let normal event handling take place.
917 rect
.CenterPoint().y() > child_at(0)->bounds().bottom()) {
921 // Special case for (CVC) inputs in the suggestion view.
922 if (forward_mouse_events_
&&
923 handler
->GetAncestorWithClassName(DecoratedTextfield::kViewClassName
)) {
927 // Special case for the proxy button itself.
928 if (handler
== proxy_button_
)
935 ui::MouseEvent
AutofillDialogViews::SectionContainer::ProxyEvent(
936 const ui::MouseEvent
& event
) {
937 ui::MouseEvent event_copy
= event
;
938 event_copy
.set_location(gfx::Point());
942 bool AutofillDialogViews::SectionContainer::ShouldForwardEvent(
943 const ui::LocatedEvent
& event
) {
944 // Always forward events on the label bar.
945 return forward_mouse_events_
|| event
.y() <= child_at(0)->bounds().bottom();
948 // AutofillDialogViews::SuggestedButton ----------------------------------------
950 AutofillDialogViews::SuggestedButton::SuggestedButton(
951 views::MenuButtonListener
* listener
)
952 : views::MenuButton(NULL
, base::string16(), listener
, false) {
953 const int kFocusBorderWidth
= 1;
954 SetBorder(views::Border::CreateEmptyBorder(kMenuButtonTopInset
,
956 kMenuButtonBottomInset
,
958 gfx::Insets insets
= GetInsets();
959 insets
+= gfx::Insets(-kFocusBorderWidth
, -kFocusBorderWidth
,
960 -kFocusBorderWidth
, -kFocusBorderWidth
);
962 views::Painter::CreateDashedFocusPainterWithInsets(insets
));
966 AutofillDialogViews::SuggestedButton::~SuggestedButton() {}
968 gfx::Size
AutofillDialogViews::SuggestedButton::GetPreferredSize() {
969 ui::ResourceBundle
& rb
= ui::ResourceBundle::GetSharedInstance();
970 gfx::Size size
= rb
.GetImageNamed(ResourceIDForState()).Size();
971 const gfx::Insets insets
= GetInsets();
972 size
.Enlarge(insets
.width(), insets
.height());
976 const char* AutofillDialogViews::SuggestedButton::GetClassName() const {
977 return kSuggestedButtonClassName
;
980 void AutofillDialogViews::SuggestedButton::PaintChildren(gfx::Canvas
* canvas
) {}
982 void AutofillDialogViews::SuggestedButton::OnPaint(gfx::Canvas
* canvas
) {
983 ui::ResourceBundle
& rb
= ui::ResourceBundle::GetSharedInstance();
984 const gfx::Insets insets
= GetInsets();
985 canvas
->DrawImageInt(*rb
.GetImageSkiaNamed(ResourceIDForState()),
986 insets
.left(), insets
.top());
987 views::Painter::PaintFocusPainter(this, canvas
, focus_painter());
990 int AutofillDialogViews::SuggestedButton::ResourceIDForState() const {
991 views::Button::ButtonState button_state
= state();
992 if (button_state
== views::Button::STATE_PRESSED
)
993 return IDR_AUTOFILL_DIALOG_MENU_BUTTON_P
;
994 else if (button_state
== views::Button::STATE_HOVERED
)
995 return IDR_AUTOFILL_DIALOG_MENU_BUTTON_H
;
996 else if (button_state
== views::Button::STATE_DISABLED
)
997 return IDR_AUTOFILL_DIALOG_MENU_BUTTON_D
;
998 DCHECK_EQ(views::Button::STATE_NORMAL
, button_state
);
999 return IDR_AUTOFILL_DIALOG_MENU_BUTTON
;
1002 // AutofillDialogViews::DetailsContainerView -----------------------------------
1004 AutofillDialogViews::DetailsContainerView::DetailsContainerView(
1005 const base::Closure
& callback
)
1006 : bounds_changed_callback_(callback
),
1007 ignore_layouts_(false) {}
1009 AutofillDialogViews::DetailsContainerView::~DetailsContainerView() {}
1011 void AutofillDialogViews::DetailsContainerView::OnBoundsChanged(
1012 const gfx::Rect
& previous_bounds
) {
1013 bounds_changed_callback_
.Run();
1016 void AutofillDialogViews::DetailsContainerView::Layout() {
1017 if (!ignore_layouts_
)
1018 views::View::Layout();
1021 // AutofillDialogViews::SuggestionView -----------------------------------------
1023 AutofillDialogViews::SuggestionView::SuggestionView(
1024 AutofillDialogViews
* autofill_dialog
)
1025 : label_(new views::Label()),
1026 label_line_2_(new views::Label()),
1027 icon_(new views::ImageView()),
1029 new DecoratedTextfield(base::string16(),
1032 // TODO(estade): Make this the correct color.
1033 SetBorder(views::Border::CreateSolidSidedBorder(1, 0, 0, 0, SK_ColorLTGRAY
));
1035 SectionRowView
* label_container
= new SectionRowView();
1036 AddChildView(label_container
);
1039 label_container
->AddChildView(icon_
);
1040 label_
->SetHorizontalAlignment(gfx::ALIGN_LEFT
);
1041 label_container
->AddChildView(label_
);
1043 // TODO(estade): get the sizing and spacing right on this textfield.
1044 decorated_
->SetVisible(false);
1045 decorated_
->set_default_width_in_chars(15);
1046 label_container
->AddChildView(decorated_
);
1048 label_line_2_
->SetHorizontalAlignment(gfx::ALIGN_LEFT
);
1049 label_line_2_
->SetVisible(false);
1050 label_line_2_
->SetLineHeight(22);
1051 label_line_2_
->SetMultiLine(true);
1052 AddChildView(label_line_2_
);
1054 SetLayoutManager(new views::BoxLayout(views::BoxLayout::kVertical
, 0, 0, 7));
1057 AutofillDialogViews::SuggestionView::~SuggestionView() {}
1059 gfx::Size
AutofillDialogViews::SuggestionView::GetPreferredSize() {
1060 // There's no preferred width. The parent's layout should get the preferred
1061 // height from GetHeightForWidth().
1065 int AutofillDialogViews::SuggestionView::GetHeightForWidth(int width
) {
1067 CanUseVerticallyCompactText(width
, &height
);
1071 bool AutofillDialogViews::SuggestionView::CanUseVerticallyCompactText(
1072 int available_width
,
1073 int* resulting_height
) {
1074 // This calculation may be costly, avoid doing it more than once per width.
1075 if (!calculated_heights_
.count(available_width
)) {
1076 // Changing the state of |this| now will lead to extra layouts and
1077 // paints we don't want, so create another SuggestionView to calculate
1078 // which label we have room to show.
1079 SuggestionView
sizing_view(NULL
);
1080 sizing_view
.SetLabelText(state_
.vertically_compact_text
);
1081 sizing_view
.SetIcon(state_
.icon
);
1082 sizing_view
.SetTextfield(state_
.extra_text
, state_
.extra_icon
);
1083 sizing_view
.label_
->SetSize(gfx::Size(available_width
, 0));
1084 sizing_view
.label_line_2_
->SetSize(gfx::Size(available_width
, 0));
1086 // Shortcut |sizing_view|'s GetHeightForWidth() to avoid an infinite loop.
1087 // Its BoxLayout must do these calculations for us.
1088 views::LayoutManager
* layout
= sizing_view
.GetLayoutManager();
1089 if (layout
->GetPreferredSize(&sizing_view
).width() <= available_width
) {
1090 calculated_heights_
[available_width
] = std::make_pair(
1092 layout
->GetPreferredHeightForWidth(&sizing_view
, available_width
));
1094 sizing_view
.SetLabelText(state_
.horizontally_compact_text
);
1095 calculated_heights_
[available_width
] = std::make_pair(
1097 layout
->GetPreferredHeightForWidth(&sizing_view
, available_width
));
1101 const std::pair
<bool, int>& values
= calculated_heights_
[available_width
];
1102 *resulting_height
= values
.second
;
1103 return values
.first
;
1106 void AutofillDialogViews::SuggestionView::OnBoundsChanged(
1107 const gfx::Rect
& previous_bounds
) {
1111 void AutofillDialogViews::SuggestionView::SetState(
1112 const SuggestionState
& state
) {
1113 calculated_heights_
.clear();
1115 SetVisible(state_
.visible
);
1117 SetIcon(state_
.icon
);
1118 SetTextfield(state_
.extra_text
, state_
.extra_icon
);
1119 PreferredSizeChanged();
1122 void AutofillDialogViews::SuggestionView::SetLabelText(
1123 const base::string16
& text
) {
1124 // TODO(estade): does this localize well?
1125 base::string16
line_return(base::ASCIIToUTF16("\n"));
1126 size_t position
= text
.find(line_return
);
1127 if (position
== base::string16::npos
) {
1128 label_
->SetText(text
);
1129 label_line_2_
->SetVisible(false);
1131 label_
->SetText(text
.substr(0, position
));
1132 label_line_2_
->SetText(text
.substr(position
+ line_return
.length()));
1133 label_line_2_
->SetVisible(true);
1137 void AutofillDialogViews::SuggestionView::SetIcon(
1138 const gfx::Image
& image
) {
1139 icon_
->SetVisible(!image
.IsEmpty());
1140 icon_
->SetImage(image
.AsImageSkia());
1143 void AutofillDialogViews::SuggestionView::SetTextfield(
1144 const base::string16
& placeholder_text
,
1145 const gfx::Image
& icon
) {
1146 decorated_
->set_placeholder_text(placeholder_text
);
1147 decorated_
->SetIcon(icon
);
1148 decorated_
->SetVisible(!placeholder_text
.empty());
1151 void AutofillDialogViews::SuggestionView::UpdateLabelText() {
1153 SetLabelText(CanUseVerticallyCompactText(width(), &unused
) ?
1154 state_
.vertically_compact_text
:
1155 state_
.horizontally_compact_text
);
1158 // AutofillDialogView ----------------------------------------------------------
1161 AutofillDialogView
* AutofillDialogView::Create(
1162 AutofillDialogViewDelegate
* delegate
) {
1163 return new AutofillDialogViews(delegate
);
1166 // AutofillDialogViews ---------------------------------------------------------
1168 AutofillDialogViews::AutofillDialogViews(AutofillDialogViewDelegate
* delegate
)
1169 : delegate_(delegate
),
1171 needs_update_(false),
1173 notification_area_(NULL
),
1174 account_chooser_(NULL
),
1175 sign_in_web_view_(NULL
),
1176 scrollable_area_(NULL
),
1177 details_container_(NULL
),
1178 loading_shield_(NULL
),
1179 loading_shield_height_(0),
1180 overlay_view_(NULL
),
1181 button_strip_extra_view_(NULL
),
1182 save_in_chrome_checkbox_(NULL
),
1183 save_in_chrome_checkbox_container_(NULL
),
1184 button_strip_image_(NULL
),
1185 footnote_view_(NULL
),
1186 legal_document_view_(NULL
),
1187 focus_manager_(NULL
),
1188 error_bubble_(NULL
),
1191 detail_groups_
.insert(std::make_pair(SECTION_CC
,
1192 DetailsGroup(SECTION_CC
)));
1193 detail_groups_
.insert(std::make_pair(SECTION_BILLING
,
1194 DetailsGroup(SECTION_BILLING
)));
1195 detail_groups_
.insert(std::make_pair(SECTION_CC_BILLING
,
1196 DetailsGroup(SECTION_CC_BILLING
)));
1197 detail_groups_
.insert(std::make_pair(SECTION_SHIPPING
,
1198 DetailsGroup(SECTION_SHIPPING
)));
1201 AutofillDialogViews::~AutofillDialogViews() {
1206 void AutofillDialogViews::Show() {
1208 UpdateAccountChooser();
1209 UpdateNotificationArea();
1210 UpdateButtonStripExtraView();
1212 // Ownership of |contents_| is handed off by this call. The widget will take
1213 // care of deleting itself after calling DeleteDelegate().
1214 WebContentsModalDialogManager
* web_contents_modal_dialog_manager
=
1215 WebContentsModalDialogManager::FromWebContents(
1216 delegate_
->GetWebContents());
1217 WebContentsModalDialogManagerDelegate
* modal_delegate
=
1218 web_contents_modal_dialog_manager
->delegate();
1219 DCHECK(modal_delegate
);
1220 window_
= views::Widget::CreateWindowAsFramelessChild(
1221 this, modal_delegate
->GetWebContentsModalDialogHost()->GetHostView());
1222 web_contents_modal_dialog_manager
->ShowDialog(window_
->GetNativeView());
1223 focus_manager_
= window_
->GetFocusManager();
1224 focus_manager_
->AddFocusChangeListener(this);
1226 ShowDialogInMode(DETAIL_INPUT
);
1228 // Listen for size changes on the browser.
1229 views::Widget
* browser_widget
=
1230 views::Widget::GetTopLevelWidgetForNativeView(
1231 delegate_
->GetWebContents()->GetView()->GetNativeView());
1232 observer_
.Add(browser_widget
);
1235 void AutofillDialogViews::Hide() {
1240 void AutofillDialogViews::UpdatesStarted() {
1244 void AutofillDialogViews::UpdatesFinished() {
1246 DCHECK_GE(updates_scope_
, 0);
1247 if (updates_scope_
== 0 && needs_update_
) {
1248 needs_update_
= false;
1249 ContentsPreferredSizeChanged();
1253 void AutofillDialogViews::UpdateAccountChooser() {
1254 account_chooser_
->Update();
1256 bool show_loading
= delegate_
->ShouldShowSpinner();
1257 if (show_loading
!= loading_shield_
->visible()) {
1259 loading_shield_height_
= std::max(kInitialLoadingShieldHeight
,
1260 GetContentsBounds().height());
1261 ShowDialogInMode(LOADING
);
1263 bool show_sign_in
= delegate_
->ShouldShowSignInWebView();
1264 ShowDialogInMode(show_sign_in
? SIGN_IN
: DETAIL_INPUT
);
1268 ContentsPreferredSizeChanged();
1271 // Update legal documents for the account.
1272 if (footnote_view_
) {
1273 const base::string16 text
= delegate_
->LegalDocumentsText();
1274 legal_document_view_
->SetText(text
);
1276 if (!text
.empty()) {
1277 const std::vector
<gfx::Range
>& link_ranges
=
1278 delegate_
->LegalDocumentLinks();
1279 for (size_t i
= 0; i
< link_ranges
.size(); ++i
) {
1280 views::StyledLabel::RangeStyleInfo link_range_info
=
1281 views::StyledLabel::RangeStyleInfo::CreateForLink();
1282 link_range_info
.disable_line_wrapping
= false;
1283 legal_document_view_
->AddStyleRange(link_ranges
[i
], link_range_info
);
1287 footnote_view_
->SetVisible(!text
.empty());
1288 ContentsPreferredSizeChanged();
1292 GetWidget()->UpdateWindowTitle();
1295 void AutofillDialogViews::UpdateButtonStrip() {
1296 button_strip_extra_view_
->SetVisible(
1297 GetDialogButtons() != ui::DIALOG_BUTTON_NONE
);
1298 UpdateButtonStripExtraView();
1299 GetDialogClientView()->UpdateDialogButtons();
1301 ContentsPreferredSizeChanged();
1304 void AutofillDialogViews::UpdateOverlay() {
1305 overlay_view_
->UpdateState();
1306 ContentsPreferredSizeChanged();
1309 void AutofillDialogViews::UpdateDetailArea() {
1310 scrollable_area_
->SetVisible(true);
1311 ContentsPreferredSizeChanged();
1314 void AutofillDialogViews::UpdateForErrors() {
1318 void AutofillDialogViews::UpdateNotificationArea() {
1319 DCHECK(notification_area_
);
1320 notification_area_
->SetNotifications(delegate_
->CurrentNotifications());
1321 ContentsPreferredSizeChanged();
1324 void AutofillDialogViews::UpdateSection(DialogSection section
) {
1325 UpdateSectionImpl(section
, true);
1328 void AutofillDialogViews::UpdateErrorBubble() {
1329 if (!delegate_
->ShouldShowErrorBubble())
1333 void AutofillDialogViews::FillSection(DialogSection section
,
1334 ServerFieldType originating_type
) {
1335 DetailsGroup
* group
= GroupForSection(section
);
1336 // Make sure to overwrite the originating input if it exists.
1337 TextfieldMap::iterator text_mapping
=
1338 group
->textfields
.find(originating_type
);
1339 if (text_mapping
!= group
->textfields
.end())
1340 text_mapping
->second
->SetText(base::string16());
1342 // If the Autofill data comes from a credit card, make sure to overwrite the
1343 // CC comboboxes (even if they already have something in them). If the
1344 // Autofill data comes from an AutofillProfile, leave the comboboxes alone.
1345 if (section
== GetCreditCardSection() &&
1346 AutofillType(originating_type
).group() == CREDIT_CARD
) {
1347 for (ComboboxMap::const_iterator it
= group
->comboboxes
.begin();
1348 it
!= group
->comboboxes
.end(); ++it
) {
1349 if (AutofillType(it
->first
).group() == CREDIT_CARD
)
1350 it
->second
->SetSelectedIndex(it
->second
->model()->GetDefaultIndex());
1354 UpdateSectionImpl(section
, false);
1357 void AutofillDialogViews::GetUserInput(DialogSection section
,
1358 FieldValueMap
* output
) {
1359 DetailsGroup
* group
= GroupForSection(section
);
1360 for (TextfieldMap::const_iterator it
= group
->textfields
.begin();
1361 it
!= group
->textfields
.end(); ++it
) {
1362 output
->insert(std::make_pair(it
->first
, it
->second
->text()));
1364 for (ComboboxMap::const_iterator it
= group
->comboboxes
.begin();
1365 it
!= group
->comboboxes
.end(); ++it
) {
1366 output
->insert(std::make_pair(it
->first
,
1367 it
->second
->model()->GetItemAt(it
->second
->selected_index())));
1371 base::string16
AutofillDialogViews::GetCvc() {
1372 return GroupForSection(GetCreditCardSection())->suggested_info
->
1373 decorated_textfield()->text();
1376 bool AutofillDialogViews::HitTestInput(ServerFieldType type
,
1377 const gfx::Point
& screen_point
) {
1378 views::View
* view
= TextfieldForType(type
);
1380 view
= ComboboxForType(type
);
1383 gfx::Point
target_point(screen_point
);
1384 views::View::ConvertPointFromScreen(view
, &target_point
);
1385 return view
->HitTestPoint(target_point
);
1392 bool AutofillDialogViews::SaveDetailsLocally() {
1393 DCHECK(save_in_chrome_checkbox_
->visible());
1394 return save_in_chrome_checkbox_
->checked();
1397 const content::NavigationController
* AutofillDialogViews::ShowSignIn() {
1398 // The initial minimum width and height are set such that the dialog
1399 // won't change size before the page is loaded.
1400 int min_width
= GetContentsBounds().width();
1401 // The height has to include the button strip.
1402 int min_height
= GetDialogClientView()->GetContentsBounds().height();
1404 // TODO(abodenha): We should be able to use the WebContents of the WebView
1405 // to navigate instead of LoadInitialURL. Figure out why it doesn't work.
1406 sign_in_delegate_
.reset(
1407 new AutofillDialogSignInDelegate(
1409 sign_in_web_view_
->GetWebContents(),
1410 delegate_
->GetWebContents(),
1411 gfx::Size(min_width
, min_height
), GetMaximumSignInViewSize()));
1412 sign_in_web_view_
->LoadInitialURL(delegate_
->SignInUrl());
1414 ShowDialogInMode(SIGN_IN
);
1416 ContentsPreferredSizeChanged();
1418 return &sign_in_web_view_
->web_contents()->GetController();
1421 void AutofillDialogViews::HideSignIn() {
1422 sign_in_web_view_
->SetWebContents(NULL
);
1424 if (delegate_
->ShouldShowSpinner()) {
1425 UpdateAccountChooser();
1427 ShowDialogInMode(DETAIL_INPUT
);
1430 DCHECK(!sign_in_web_view_
->visible());
1432 ContentsPreferredSizeChanged();
1435 void AutofillDialogViews::ModelChanged() {
1436 menu_runner_
.reset();
1439 void AutofillDialogViews::OnSignInResize(const gfx::Size
& pref_size
) {
1440 sign_in_web_view_
->SetPreferredSize(pref_size
);
1441 ContentsPreferredSizeChanged();
1444 void AutofillDialogViews::ValidateSection(DialogSection section
) {
1445 ValidateGroup(*GroupForSection(section
), VALIDATE_EDIT
);
1448 gfx::Size
AutofillDialogViews::GetPreferredSize() {
1449 if (preferred_size_
.IsEmpty())
1450 preferred_size_
= CalculatePreferredSize(false);
1452 return preferred_size_
;
1455 gfx::Size
AutofillDialogViews::GetMinimumSize() {
1456 return CalculatePreferredSize(true);
1459 void AutofillDialogViews::Layout() {
1460 const gfx::Rect content_bounds
= GetContentsBounds();
1461 if (sign_in_web_view_
->visible()) {
1462 sign_in_web_view_
->SetBoundsRect(content_bounds
);
1466 if (loading_shield_
->visible()) {
1467 loading_shield_
->SetBoundsRect(bounds());
1471 const int x
= content_bounds
.x();
1472 const int y
= content_bounds
.y();
1473 const int width
= content_bounds
.width();
1474 // Layout notification area at top of dialog.
1475 int notification_height
= notification_area_
->GetHeightForWidth(width
);
1476 notification_area_
->SetBounds(x
, y
, width
, notification_height
);
1478 // The rest (the |scrollable_area_|) takes up whatever's left.
1479 if (scrollable_area_
->visible()) {
1481 if (notification_height
> notification_area_
->GetInsets().height())
1482 scroll_y
+= notification_height
+ views::kRelatedControlVerticalSpacing
;
1484 int scroll_bottom
= content_bounds
.bottom();
1485 DCHECK_EQ(scrollable_area_
->contents(), details_container_
);
1486 details_container_
->SizeToPreferredSize();
1487 details_container_
->Layout();
1488 // TODO(estade): remove this hack. See crbug.com/285996
1489 details_container_
->set_ignore_layouts(true);
1490 scrollable_area_
->SetBounds(x
, scroll_y
, width
, scroll_bottom
- scroll_y
);
1491 details_container_
->set_ignore_layouts(false);
1495 error_bubble_
->UpdatePosition();
1498 void AutofillDialogViews::OnNativeThemeChanged(
1499 const ui::NativeTheme
* theme
) {
1500 if (!legal_document_view_
)
1503 // NOTE: This color may change because of |auto_color_readability|, set on
1504 // |legal_document_view_|.
1505 views::StyledLabel::RangeStyleInfo default_style
;
1506 default_style
.color
=
1507 theme
->GetSystemColor(ui::NativeTheme::kColorId_LabelDisabledColor
);
1509 legal_document_view_
->SetDefaultStyle(default_style
);
1512 base::string16
AutofillDialogViews::GetWindowTitle() const {
1513 base::string16 title
= delegate_
->DialogTitle();
1514 // Hack alert: we don't want the dialog to jiggle when a title is added or
1515 // removed. Setting a non-empty string here keeps the dialog's title bar the
1517 return title
.empty() ? base::ASCIIToUTF16(" ") : title
;
1520 void AutofillDialogViews::WindowClosing() {
1521 focus_manager_
->RemoveFocusChangeListener(this);
1524 void AutofillDialogViews::DeleteDelegate() {
1526 // |this| belongs to the controller (|delegate_|).
1527 delegate_
->ViewClosed();
1530 int AutofillDialogViews::GetDialogButtons() const {
1531 return delegate_
->GetDialogButtons();
1534 int AutofillDialogViews::GetDefaultDialogButton() const {
1535 if (GetDialogButtons() & ui::DIALOG_BUTTON_OK
)
1536 return ui::DIALOG_BUTTON_OK
;
1538 return ui::DIALOG_BUTTON_NONE
;
1541 base::string16
AutofillDialogViews::GetDialogButtonLabel(
1542 ui::DialogButton button
) const {
1543 return button
== ui::DIALOG_BUTTON_OK
?
1544 delegate_
->ConfirmButtonText() : delegate_
->CancelButtonText();
1547 bool AutofillDialogViews::ShouldDefaultButtonBeBlue() const {
1551 bool AutofillDialogViews::IsDialogButtonEnabled(ui::DialogButton button
) const {
1552 return delegate_
->IsDialogButtonEnabled(button
);
1555 views::View
* AutofillDialogViews::GetInitiallyFocusedView() {
1556 if (!window_
|| !focus_manager_
)
1559 if (sign_in_web_view_
->visible())
1560 return sign_in_web_view_
;
1562 if (loading_shield_
->visible())
1563 return views::DialogDelegateView::GetInitiallyFocusedView();
1565 DCHECK(scrollable_area_
->visible());
1567 views::FocusManager
* manager
= focus_manager_
;
1568 for (views::View
* next
= scrollable_area_
;
1570 next
= manager
->GetNextFocusableView(next
, window_
, false, true)) {
1574 // If there are no invalid inputs, return the first input found. Otherwise,
1575 // return the first invalid input found.
1576 if (validity_map_
.empty() ||
1577 validity_map_
.find(next
) != validity_map_
.end()) {
1582 return views::DialogDelegateView::GetInitiallyFocusedView();
1585 views::View
* AutofillDialogViews::CreateExtraView() {
1586 return button_strip_extra_view_
;
1589 views::View
* AutofillDialogViews::CreateTitlebarExtraView() {
1590 return account_chooser_
;
1593 views::View
* AutofillDialogViews::CreateFootnoteView() {
1594 footnote_view_
= new LayoutPropagationView();
1595 footnote_view_
->SetLayoutManager(
1596 new views::BoxLayout(views::BoxLayout::kVertical
,
1600 footnote_view_
->SetBorder(
1601 views::Border::CreateSolidSidedBorder(1, 0, 0, 0, kSubtleBorderColor
));
1602 footnote_view_
->set_background(
1603 views::Background::CreateSolidBackground(kShadingColor
));
1605 legal_document_view_
= new views::StyledLabel(base::string16(), this);
1606 OnNativeThemeChanged(GetNativeTheme());
1608 footnote_view_
->AddChildView(legal_document_view_
);
1609 footnote_view_
->SetVisible(false);
1611 return footnote_view_
;
1614 views::View
* AutofillDialogViews::CreateOverlayView() {
1615 return overlay_view_
;
1618 bool AutofillDialogViews::Cancel() {
1619 return delegate_
->OnCancel();
1622 bool AutofillDialogViews::Accept() {
1624 return delegate_
->OnAccept();
1626 // |ValidateForm()| failed; there should be invalid views in |validity_map_|.
1627 DCHECK(!validity_map_
.empty());
1633 void AutofillDialogViews::ContentsChanged(views::Textfield
* sender
,
1634 const base::string16
& new_contents
) {
1635 InputEditedOrActivated(TypeForTextfield(sender
),
1636 sender
->GetBoundsInScreen(),
1640 bool AutofillDialogViews::HandleKeyEvent(views::Textfield
* sender
,
1641 const ui::KeyEvent
& key_event
) {
1642 ui::KeyEvent
copy(key_event
);
1643 content::NativeWebKeyboardEvent
event(©
);
1644 return delegate_
->HandleKeyPressEventInInput(event
);
1647 bool AutofillDialogViews::HandleMouseEvent(views::Textfield
* sender
,
1648 const ui::MouseEvent
& mouse_event
) {
1649 if (mouse_event
.IsLeftMouseButton() && sender
->HasFocus()) {
1650 InputEditedOrActivated(TypeForTextfield(sender
),
1651 sender
->GetBoundsInScreen(),
1653 // Show an error bubble if a user clicks on an input that's already focused
1655 ShowErrorBubbleForViewIfNecessary(sender
);
1661 void AutofillDialogViews::OnWillChangeFocus(
1662 views::View
* focused_before
,
1663 views::View
* focused_now
) {
1664 delegate_
->FocusMoved();
1668 void AutofillDialogViews::OnDidChangeFocus(
1669 views::View
* focused_before
,
1670 views::View
* focused_now
) {
1671 // If user leaves an edit-field, revalidate the group it belongs to.
1672 if (focused_before
) {
1673 DetailsGroup
* group
= GroupForView(focused_before
);
1674 if (group
&& group
->container
->visible())
1675 ValidateGroup(*group
, VALIDATE_EDIT
);
1678 // Show an error bubble when the user focuses the input.
1680 focused_now
->ScrollRectToVisible(focused_now
->GetLocalBounds());
1681 ShowErrorBubbleForViewIfNecessary(focused_now
);
1685 void AutofillDialogViews::OnPerformAction(views::Combobox
* combobox
) {
1686 DialogSection section
= GroupForView(combobox
)->section
;
1687 InputEditedOrActivated(TypeForCombobox(combobox
), gfx::Rect(), true);
1688 // NOTE: |combobox| may have been deleted.
1689 ValidateGroup(*GroupForSection(section
), VALIDATE_EDIT
);
1690 SetEditabilityForSection(section
);
1693 void AutofillDialogViews::StyledLabelLinkClicked(const gfx::Range
& range
,
1695 delegate_
->LegalDocumentLinkClicked(range
);
1698 void AutofillDialogViews::OnMenuButtonClicked(views::View
* source
,
1699 const gfx::Point
& point
) {
1700 DCHECK_EQ(kSuggestedButtonClassName
, source
->GetClassName());
1702 DetailsGroup
* group
= NULL
;
1703 for (DetailGroupMap::iterator iter
= detail_groups_
.begin();
1704 iter
!= detail_groups_
.end(); ++iter
) {
1705 if (source
== iter
->second
.suggested_button
) {
1706 group
= &iter
->second
;
1712 if (!group
->suggested_button
->visible())
1715 menu_runner_
.reset(new views::MenuRunner(
1716 delegate_
->MenuModelForSection(group
->section
)));
1718 group
->container
->SetActive(true);
1719 views::Button::ButtonState state
= group
->suggested_button
->state();
1720 group
->suggested_button
->SetState(views::Button::STATE_PRESSED
);
1722 gfx::Rect screen_bounds
= source
->GetBoundsInScreen();
1723 screen_bounds
.Inset(source
->GetInsets());
1724 if (menu_runner_
->RunMenuAt(source
->GetWidget(),
1727 views::MenuItemView::TOPRIGHT
,
1728 ui::MENU_SOURCE_NONE
,
1729 0) == views::MenuRunner::MENU_DELETED
) {
1733 group
->container
->SetActive(false);
1734 group
->suggested_button
->SetState(state
);
1737 gfx::Size
AutofillDialogViews::CalculatePreferredSize(bool get_minimum_size
) {
1738 gfx::Insets insets
= GetInsets();
1739 gfx::Size scroll_size
= scrollable_area_
->contents()->GetPreferredSize();
1740 // The width is always set by the scroll area.
1741 const int width
= scroll_size
.width();
1743 if (sign_in_web_view_
->visible()) {
1744 const gfx::Size size
= static_cast<views::View
*>(sign_in_web_view_
)->
1746 return gfx::Size(width
+ insets
.width(), size
.height() + insets
.height());
1749 if (overlay_view_
->visible()) {
1750 const int height
= overlay_view_
->GetHeightForContentsForWidth(width
);
1752 return gfx::Size(width
+ insets
.width(), height
+ insets
.height());
1755 if (loading_shield_
->visible()) {
1756 return gfx::Size(width
+ insets
.width(),
1757 loading_shield_height_
+ insets
.height());
1761 const int notification_height
= notification_area_
->GetHeightForWidth(width
);
1762 if (notification_height
> notification_area_
->GetInsets().height())
1763 height
+= notification_height
+ views::kRelatedControlVerticalSpacing
;
1765 if (scrollable_area_
->visible())
1766 height
+= get_minimum_size
? kMinimumContentsHeight
: scroll_size
.height();
1768 return gfx::Size(width
+ insets
.width(), height
+ insets
.height());
1771 gfx::Size
AutofillDialogViews::GetMinimumSignInViewSize() const {
1772 return gfx::Size(GetDialogClientView()->size().width() - GetInsets().width(),
1773 kMinimumContentsHeight
);
1776 gfx::Size
AutofillDialogViews::GetMaximumSignInViewSize() const {
1777 web_modal::WebContentsModalDialogHost
* dialog_host
=
1778 WebContentsModalDialogManager::FromWebContents(
1779 delegate_
->GetWebContents())->delegate()->
1780 GetWebContentsModalDialogHost();
1782 // Inset the maximum dialog height to get the maximum content height.
1783 int height
= dialog_host
->GetMaximumDialogSize().height();
1784 const int non_client_height
= GetWidget()->non_client_view()->height();
1785 const int client_height
= GetWidget()->client_view()->height();
1786 // TODO(msw): Resolve the 12 pixel discrepancy; is that the bubble border?
1787 height
-= non_client_height
- client_height
- 12;
1788 height
= std::max(height
, kMinimumContentsHeight
);
1790 // The dialog's width never changes.
1791 const int width
= GetDialogClientView()->size().width() - GetInsets().width();
1792 return gfx::Size(width
, height
);
1795 DialogSection
AutofillDialogViews::GetCreditCardSection() const {
1796 if (delegate_
->SectionIsActive(SECTION_CC
))
1799 DCHECK(delegate_
->SectionIsActive(SECTION_CC_BILLING
));
1800 return SECTION_CC_BILLING
;
1803 void AutofillDialogViews::InitChildViews() {
1804 button_strip_extra_view_
= new LayoutPropagationView();
1805 button_strip_extra_view_
->SetLayoutManager(
1806 new views::BoxLayout(views::BoxLayout::kHorizontal
, 0, 0, 0));
1808 save_in_chrome_checkbox_container_
= new views::View();
1809 save_in_chrome_checkbox_container_
->SetLayoutManager(
1810 new views::BoxLayout(views::BoxLayout::kHorizontal
, 0, 0, 7));
1811 button_strip_extra_view_
->AddChildView(save_in_chrome_checkbox_container_
);
1813 save_in_chrome_checkbox_
=
1814 new views::Checkbox(delegate_
->SaveLocallyText());
1815 save_in_chrome_checkbox_
->SetChecked(delegate_
->ShouldSaveInChrome());
1816 save_in_chrome_checkbox_container_
->AddChildView(save_in_chrome_checkbox_
);
1818 save_in_chrome_checkbox_container_
->AddChildView(
1819 new TooltipIcon(delegate_
->SaveLocallyTooltip()));
1821 button_strip_image_
= new views::ImageView();
1822 button_strip_extra_view_
->AddChildView(button_strip_image_
);
1824 account_chooser_
= new AccountChooser(delegate_
);
1825 notification_area_
= new NotificationArea(delegate_
);
1826 notification_area_
->set_arrow_centering_anchor(account_chooser_
->AsWeakPtr());
1827 AddChildView(notification_area_
);
1829 scrollable_area_
= new views::ScrollView();
1830 scrollable_area_
->set_hide_horizontal_scrollbar(true);
1831 scrollable_area_
->SetContents(CreateDetailsContainer());
1832 AddChildView(scrollable_area_
);
1834 loading_shield_
= new LoadingAnimationView(delegate_
->SpinnerText());
1835 AddChildView(loading_shield_
);
1837 sign_in_web_view_
= new views::WebView(delegate_
->profile());
1838 AddChildView(sign_in_web_view_
);
1840 overlay_view_
= new OverlayView(delegate_
);
1841 overlay_view_
->SetVisible(false);
1844 views::View
* AutofillDialogViews::CreateDetailsContainer() {
1845 details_container_
= new DetailsContainerView(
1846 base::Bind(&AutofillDialogViews::DetailsContainerBoundsChanged
,
1847 base::Unretained(this)));
1849 // A box layout is used because it respects widget visibility.
1850 details_container_
->SetLayoutManager(
1851 new views::BoxLayout(views::BoxLayout::kVertical
, 0, 0, 0));
1852 for (DetailGroupMap::iterator iter
= detail_groups_
.begin();
1853 iter
!= detail_groups_
.end(); ++iter
) {
1854 CreateDetailsSection(iter
->second
.section
);
1855 details_container_
->AddChildView(iter
->second
.container
);
1858 return details_container_
;
1861 void AutofillDialogViews::CreateDetailsSection(DialogSection section
) {
1862 // Inputs container (manual inputs + combobox).
1863 views::View
* inputs_container
= CreateInputsContainer(section
);
1865 DetailsGroup
* group
= GroupForSection(section
);
1866 // Container (holds label + inputs).
1867 group
->container
= new SectionContainer(delegate_
->LabelForSection(section
),
1869 group
->suggested_button
);
1870 DCHECK(group
->suggested_button
->parent());
1871 UpdateDetailsGroupState(*group
);
1874 views::View
* AutofillDialogViews::CreateInputsContainer(DialogSection section
) {
1875 // The |info_view| holds |manual_inputs| and |suggested_info|, allowing the
1876 // dialog to toggle which is shown.
1877 views::View
* info_view
= new views::View();
1878 info_view
->SetLayoutManager(
1879 new views::BoxLayout(views::BoxLayout::kVertical
, 0, 0, 0));
1881 DetailsGroup
* group
= GroupForSection(section
);
1882 group
->manual_input
= new views::View();
1883 InitInputsView(section
);
1884 info_view
->AddChildView(group
->manual_input
);
1886 group
->suggested_info
= new SuggestionView(this);
1887 info_view
->AddChildView(group
->suggested_info
);
1889 // TODO(estade): It might be slightly more OO if this button were created
1890 // and listened to by the section container.
1891 group
->suggested_button
= new SuggestedButton(this);
1896 // TODO(estade): we should be using Chrome-style constrained window padding
1898 void AutofillDialogViews::InitInputsView(DialogSection section
) {
1899 DetailsGroup
* group
= GroupForSection(section
);
1900 EraseInvalidViewsInGroup(group
);
1902 TextfieldMap
* textfields
= &group
->textfields
;
1903 textfields
->clear();
1905 ComboboxMap
* comboboxes
= &group
->comboboxes
;
1906 comboboxes
->clear();
1908 views::View
* view
= group
->manual_input
;
1909 view
->RemoveAllChildViews(true);
1911 views::GridLayout
* layout
= new views::GridLayout(view
);
1912 view
->SetLayoutManager(layout
);
1914 int column_set_id
= 0;
1915 const DetailInputs
& inputs
= delegate_
->RequestedFieldsForSection(section
);
1916 for (DetailInputs::const_iterator it
= inputs
.begin();
1917 it
!= inputs
.end(); ++it
) {
1918 const DetailInput
& input
= *it
;
1920 ui::ComboboxModel
* input_model
=
1921 delegate_
->ComboboxModelForAutofillType(input
.type
);
1922 scoped_ptr
<views::View
> view_to_add
;
1924 views::Combobox
* combobox
= new views::Combobox(input_model
);
1925 combobox
->set_listener(this);
1926 comboboxes
->insert(std::make_pair(input
.type
, combobox
));
1927 SelectComboboxValueOrSetToDefault(combobox
, input
.initial_value
);
1928 view_to_add
.reset(combobox
);
1930 DecoratedTextfield
* field
= new DecoratedTextfield(input
.initial_value
,
1931 input
.placeholder_text
,
1933 textfields
->insert(std::make_pair(input
.type
, field
));
1934 view_to_add
.reset(field
);
1937 if (input
.length
== DetailInput::NONE
) {
1938 other_owned_views_
.push_back(view_to_add
.release());
1942 if (input
.length
== DetailInput::LONG
)
1945 views::ColumnSet
* column_set
= layout
->GetColumnSet(column_set_id
);
1947 // Create a new column set and row.
1948 column_set
= layout
->AddColumnSet(column_set_id
);
1949 if (it
!= inputs
.begin())
1950 layout
->AddPaddingRow(0, kManualInputRowPadding
);
1951 layout
->StartRow(0, column_set_id
);
1953 // Add a new column to existing row.
1954 column_set
->AddPaddingColumn(0, views::kRelatedControlHorizontalSpacing
);
1955 // Must explicitly skip the padding column since we've already started
1957 layout
->SkipColumns(1);
1960 float expand
= input
.expand_weight
;
1961 column_set
->AddColumn(views::GridLayout::FILL
,
1962 views::GridLayout::FILL
,
1963 expand
? expand
: 1.0,
1964 views::GridLayout::USE_PREF
,
1968 // This is the same as AddView(view_to_add), except that 1 is used for the
1969 // view's preferred width. Thus the width of the column completely depends
1971 layout
->AddView(view_to_add
.release(), 1, 1,
1972 views::GridLayout::FILL
, views::GridLayout::FILL
,
1975 if (input
.length
== DetailInput::LONG
||
1976 input
.length
== DetailInput::SHORT_EOL
) {
1981 SetIconsForSection(section
);
1984 void AutofillDialogViews::ShowDialogInMode(DialogMode dialog_mode
) {
1985 loading_shield_
->SetVisible(dialog_mode
== LOADING
);
1986 sign_in_web_view_
->SetVisible(dialog_mode
== SIGN_IN
);
1987 notification_area_
->SetVisible(dialog_mode
== DETAIL_INPUT
);
1988 scrollable_area_
->SetVisible(dialog_mode
== DETAIL_INPUT
);
1992 void AutofillDialogViews::UpdateSectionImpl(
1993 DialogSection section
,
1994 bool clobber_inputs
) {
1995 DetailsGroup
* group
= GroupForSection(section
);
1997 if (clobber_inputs
) {
1998 InitInputsView(section
);
2000 const DetailInputs
& updated_inputs
=
2001 delegate_
->RequestedFieldsForSection(section
);
2003 for (DetailInputs::const_iterator iter
= updated_inputs
.begin();
2004 iter
!= updated_inputs
.end(); ++iter
) {
2005 const DetailInput
& input
= *iter
;
2007 TextfieldMap::iterator text_mapping
= group
->textfields
.find(input
.type
);
2008 if (text_mapping
!= group
->textfields
.end()) {
2009 DecoratedTextfield
* decorated
= text_mapping
->second
;
2010 if (decorated
->text().empty())
2011 decorated
->SetText(input
.initial_value
);
2014 ComboboxMap::iterator combo_mapping
= group
->comboboxes
.find(input
.type
);
2015 if (combo_mapping
!= group
->comboboxes
.end()) {
2016 views::Combobox
* combobox
= combo_mapping
->second
;
2017 if (combobox
->selected_index() == combobox
->model()->GetDefaultIndex())
2018 SelectComboboxValueOrSetToDefault(combobox
, input
.initial_value
);
2022 SetIconsForSection(section
);
2025 SetEditabilityForSection(section
);
2026 UpdateDetailsGroupState(*group
);
2029 void AutofillDialogViews::UpdateDetailsGroupState(const DetailsGroup
& group
) {
2030 const SuggestionState
& suggestion_state
=
2031 delegate_
->SuggestionStateForSection(group
.section
);
2032 group
.suggested_info
->SetState(suggestion_state
);
2033 group
.manual_input
->SetVisible(!suggestion_state
.visible
);
2035 UpdateButtonStripExtraView();
2037 const bool has_menu
= !!delegate_
->MenuModelForSection(group
.section
);
2039 if (group
.suggested_button
)
2040 group
.suggested_button
->SetVisible(has_menu
);
2042 if (group
.container
) {
2043 group
.container
->SetForwardMouseEvents(
2044 has_menu
&& suggestion_state
.visible
);
2045 group
.container
->SetVisible(delegate_
->SectionIsActive(group
.section
));
2046 if (group
.container
->visible())
2047 ValidateGroup(group
, VALIDATE_EDIT
);
2050 ContentsPreferredSizeChanged();
2053 void AutofillDialogViews::FocusInitialView() {
2054 views::View
* to_focus
= GetInitiallyFocusedView();
2055 if (to_focus
&& !to_focus
->HasFocus())
2056 to_focus
->RequestFocus();
2060 void AutofillDialogViews::SetValidityForInput(
2062 const base::string16
& message
) {
2063 bool invalid
= !message
.empty();
2064 input
->SetInvalid(invalid
);
2067 validity_map_
[input
] = message
;
2069 validity_map_
.erase(input
);
2071 if (error_bubble_
&& error_bubble_
->anchor() == input
) {
2072 validity_map_
.erase(input
);
2078 void AutofillDialogViews::ShowErrorBubbleForViewIfNecessary(views::View
* view
) {
2079 if (!view
->GetWidget())
2082 if (!delegate_
->ShouldShowErrorBubble()) {
2083 DCHECK(!error_bubble_
);
2087 std::map
<views::View
*, base::string16
>::iterator error_message
=
2088 validity_map_
.find(view
);
2089 if (error_message
!= validity_map_
.end()) {
2090 view
->ScrollRectToVisible(view
->GetLocalBounds());
2092 if (!error_bubble_
|| error_bubble_
->anchor() != view
) {
2094 error_bubble_
= new InfoBubble(view
, error_message
->second
);
2095 error_bubble_
->set_align_to_anchor_edge(true);
2096 error_bubble_
->set_preferred_width(
2097 (kSectionContainerWidth
- views::kRelatedControlVerticalSpacing
) / 2);
2098 bool show_above
= view
->GetClassName() == views::Combobox::kViewClassName
;
2099 error_bubble_
->set_show_above_anchor(show_above
);
2100 error_bubble_
->Show();
2101 observer_
.Add(error_bubble_
->GetWidget());
2106 void AutofillDialogViews::HideErrorBubble() {
2108 error_bubble_
->Hide();
2111 void AutofillDialogViews::MarkInputsInvalid(
2112 DialogSection section
,
2113 const ValidityMessages
& messages
,
2114 bool overwrite_unsure
) {
2115 DetailsGroup
* group
= GroupForSection(section
);
2116 DCHECK(group
->container
->visible());
2118 if (group
->manual_input
->visible()) {
2119 for (TextfieldMap::const_iterator iter
= group
->textfields
.begin();
2120 iter
!= group
->textfields
.end(); ++iter
) {
2121 const ValidityMessage
& message
=
2122 messages
.GetMessageOrDefault(iter
->first
);
2123 if (overwrite_unsure
|| message
.sure
)
2124 SetValidityForInput(iter
->second
, message
.text
);
2126 for (ComboboxMap::const_iterator iter
= group
->comboboxes
.begin();
2127 iter
!= group
->comboboxes
.end(); ++iter
) {
2128 const ValidityMessage
& message
=
2129 messages
.GetMessageOrDefault(iter
->first
);
2130 if (overwrite_unsure
|| message
.sure
)
2131 SetValidityForInput(iter
->second
, message
.text
);
2134 EraseInvalidViewsInGroup(group
);
2136 if (section
== GetCreditCardSection()) {
2137 // Special case CVC as it's not part of |group->manual_input|.
2138 const ValidityMessage
& message
=
2139 messages
.GetMessageOrDefault(CREDIT_CARD_VERIFICATION_CODE
);
2140 if (overwrite_unsure
|| message
.sure
) {
2141 SetValidityForInput(group
->suggested_info
->decorated_textfield(),
2148 bool AutofillDialogViews::ValidateGroup(const DetailsGroup
& group
,
2149 ValidationType validation_type
) {
2150 DCHECK(group
.container
->visible());
2152 FieldValueMap detail_outputs
;
2154 if (group
.manual_input
->visible()) {
2155 for (TextfieldMap::const_iterator iter
= group
.textfields
.begin();
2156 iter
!= group
.textfields
.end(); ++iter
) {
2157 if (!iter
->second
->editable())
2160 detail_outputs
[iter
->first
] = iter
->second
->text();
2162 for (ComboboxMap::const_iterator iter
= group
.comboboxes
.begin();
2163 iter
!= group
.comboboxes
.end(); ++iter
) {
2164 if (!iter
->second
->enabled())
2167 views::Combobox
* combobox
= iter
->second
;
2168 base::string16 item
=
2169 combobox
->model()->GetItemAt(combobox
->selected_index());
2170 detail_outputs
[iter
->first
] = item
;
2172 } else if (group
.section
== GetCreditCardSection()) {
2173 DecoratedTextfield
* decorated_cvc
=
2174 group
.suggested_info
->decorated_textfield();
2175 if (decorated_cvc
->visible())
2176 detail_outputs
[CREDIT_CARD_VERIFICATION_CODE
] = decorated_cvc
->text();
2179 ValidityMessages validity
= delegate_
->InputsAreValid(group
.section
,
2181 MarkInputsInvalid(group
.section
, validity
, validation_type
== VALIDATE_FINAL
);
2183 // If there are any validation errors, sure or unsure, the group is invalid.
2184 return !validity
.HasErrors();
2187 bool AutofillDialogViews::ValidateForm() {
2188 bool all_valid
= true;
2189 validity_map_
.clear();
2191 for (DetailGroupMap::iterator iter
= detail_groups_
.begin();
2192 iter
!= detail_groups_
.end(); ++iter
) {
2193 const DetailsGroup
& group
= iter
->second
;
2194 if (!group
.container
->visible())
2197 if (!ValidateGroup(group
, VALIDATE_FINAL
))
2204 void AutofillDialogViews::InputEditedOrActivated(ServerFieldType type
,
2205 const gfx::Rect
& bounds
,
2207 DCHECK_NE(UNKNOWN_TYPE
, type
);
2209 DecoratedTextfield
* decorated
= TextfieldForType(type
);
2210 views::Combobox
* combobox
= ComboboxForType(type
);
2212 // Both views may be NULL if the event comes from an inactive section, which
2213 // may occur when using an IME.
2214 if (!combobox
&& !decorated
)
2217 DCHECK_NE(!!combobox
, !!decorated
);
2218 DetailsGroup
* group
= decorated
? GroupForView(decorated
) :
2219 GroupForView(combobox
);
2220 base::string16 text
= decorated
?
2222 combobox
->model()->GetItemAt(combobox
->selected_index());
2225 delegate_
->UserEditedOrActivatedInput(group
->section
,
2227 GetWidget()->GetNativeView(),
2232 // If the field is a textfield and is invalid, check if the text is now valid.
2233 // Many fields (i.e. CC#) are invalid for most of the duration of editing,
2234 // so flagging them as invalid prematurely is not helpful. However,
2235 // correcting a minor mistake (i.e. a wrong CC digit) should immediately
2236 // result in validation - positive user feedback.
2237 if (decorated
&& decorated
->invalid() && was_edit
) {
2238 SetValidityForInput(
2240 delegate_
->InputValidityMessage(
2241 group
->section
, type
, decorated
->text()));
2243 // If the field transitioned from invalid to valid, re-validate the group,
2244 // since inter-field checks become meaningful with valid fields.
2245 if (!decorated
->invalid())
2246 ValidateGroup(*group
, VALIDATE_EDIT
);
2249 if (delegate_
->FieldControlsIcons(type
))
2250 SetIconsForSection(group
->section
);
2252 SetEditabilityForSection(group
->section
);
2255 void AutofillDialogViews::UpdateButtonStripExtraView() {
2256 save_in_chrome_checkbox_container_
->SetVisible(
2257 delegate_
->ShouldOfferToSaveInChrome());
2259 gfx::Image image
= delegate_
->ButtonStripImage();
2260 button_strip_image_
->SetVisible(!image
.IsEmpty());
2261 button_strip_image_
->SetImage(image
.AsImageSkia());
2264 void AutofillDialogViews::ContentsPreferredSizeChanged() {
2265 if (updates_scope_
!= 0) {
2266 needs_update_
= true;
2270 preferred_size_
= gfx::Size();
2272 if (GetWidget() && delegate_
&& delegate_
->GetWebContents()) {
2273 UpdateWebContentsModalDialogPosition(
2275 WebContentsModalDialogManager::FromWebContents(
2276 delegate_
->GetWebContents())->delegate()->
2277 GetWebContentsModalDialogHost());
2278 SetBoundsRect(bounds());
2282 AutofillDialogViews::DetailsGroup
* AutofillDialogViews::GroupForSection(
2283 DialogSection section
) {
2284 return &detail_groups_
.find(section
)->second
;
2287 AutofillDialogViews::DetailsGroup
* AutofillDialogViews::GroupForView(
2288 views::View
* view
) {
2291 for (DetailGroupMap::iterator iter
= detail_groups_
.begin();
2292 iter
!= detail_groups_
.end(); ++iter
) {
2293 DetailsGroup
* group
= &iter
->second
;
2294 if (view
->parent() == group
->manual_input
)
2297 views::View
* decorated
=
2298 view
->GetAncestorWithClassName(DecoratedTextfield::kViewClassName
);
2300 // Textfields need to check a second case, since they can be suggested
2301 // inputs instead of directly editable inputs. Those are accessed via
2302 // |suggested_info|.
2304 decorated
== group
->suggested_info
->decorated_textfield()) {
2312 void AutofillDialogViews::EraseInvalidViewsInGroup(const DetailsGroup
* group
) {
2313 std::map
<views::View
*, base::string16
>::iterator it
= validity_map_
.begin();
2314 while (it
!= validity_map_
.end()) {
2315 if (GroupForView(it
->first
) == group
)
2316 validity_map_
.erase(it
++);
2322 DecoratedTextfield
* AutofillDialogViews::TextfieldForType(
2323 ServerFieldType type
) {
2324 if (type
== CREDIT_CARD_VERIFICATION_CODE
) {
2325 DetailsGroup
* group
= GroupForSection(GetCreditCardSection());
2326 if (!group
->manual_input
->visible())
2327 return group
->suggested_info
->decorated_textfield();
2330 for (DetailGroupMap::iterator iter
= detail_groups_
.begin();
2331 iter
!= detail_groups_
.end(); ++iter
) {
2332 const DetailsGroup
& group
= iter
->second
;
2333 if (!delegate_
->SectionIsActive(group
.section
))
2336 TextfieldMap::const_iterator text_mapping
= group
.textfields
.find(type
);
2337 if (text_mapping
!= group
.textfields
.end())
2338 return text_mapping
->second
;
2344 ServerFieldType
AutofillDialogViews::TypeForTextfield(
2345 const views::Textfield
* textfield
) {
2346 DetailsGroup
* cc_group
= GroupForSection(GetCreditCardSection());
2347 if (textfield
== cc_group
->suggested_info
->decorated_textfield())
2348 return CREDIT_CARD_VERIFICATION_CODE
;
2350 for (DetailGroupMap::const_iterator it
= detail_groups_
.begin();
2351 it
!= detail_groups_
.end(); ++it
) {
2352 if (!delegate_
->SectionIsActive(it
->second
.section
))
2355 for (TextfieldMap::const_iterator text_it
= it
->second
.textfields
.begin();
2356 text_it
!= it
->second
.textfields
.end(); ++text_it
) {
2357 if (textfield
== text_it
->second
)
2358 return text_it
->first
;
2362 return UNKNOWN_TYPE
;
2365 views::Combobox
* AutofillDialogViews::ComboboxForType(
2366 ServerFieldType type
) {
2367 for (DetailGroupMap::iterator iter
= detail_groups_
.begin();
2368 iter
!= detail_groups_
.end(); ++iter
) {
2369 const DetailsGroup
& group
= iter
->second
;
2370 if (!delegate_
->SectionIsActive(group
.section
))
2373 ComboboxMap::const_iterator combo_mapping
= group
.comboboxes
.find(type
);
2374 if (combo_mapping
!= group
.comboboxes
.end())
2375 return combo_mapping
->second
;
2381 ServerFieldType
AutofillDialogViews::TypeForCombobox(
2382 const views::Combobox
* combobox
) const {
2383 for (DetailGroupMap::const_iterator it
= detail_groups_
.begin();
2384 it
!= detail_groups_
.end(); ++it
) {
2385 const DetailsGroup
& group
= it
->second
;
2386 if (!delegate_
->SectionIsActive(group
.section
))
2389 for (ComboboxMap::const_iterator combo_it
= group
.comboboxes
.begin();
2390 combo_it
!= group
.comboboxes
.end(); ++combo_it
) {
2391 if (combo_it
->second
== combobox
)
2392 return combo_it
->first
;
2396 return UNKNOWN_TYPE
;
2399 void AutofillDialogViews::DetailsContainerBoundsChanged() {
2401 error_bubble_
->UpdatePosition();
2404 void AutofillDialogViews::SetIconsForSection(DialogSection section
) {
2405 FieldValueMap user_input
;
2406 GetUserInput(section
, &user_input
);
2407 FieldIconMap field_icons
= delegate_
->IconsForFields(user_input
);
2408 TextfieldMap
* textfields
= &GroupForSection(section
)->textfields
;
2409 for (TextfieldMap::const_iterator textfield_it
= textfields
->begin();
2410 textfield_it
!= textfields
->end();
2412 ServerFieldType field_type
= textfield_it
->first
;
2413 FieldIconMap::const_iterator field_icon_it
= field_icons
.find(field_type
);
2414 DecoratedTextfield
* textfield
= textfield_it
->second
;
2415 if (field_icon_it
!= field_icons
.end())
2416 textfield
->SetIcon(field_icon_it
->second
);
2418 textfield
->SetTooltipIcon(delegate_
->TooltipForField(field_type
));
2422 void AutofillDialogViews::SetEditabilityForSection(DialogSection section
) {
2423 const DetailInputs
& inputs
=
2424 delegate_
->RequestedFieldsForSection(section
);
2425 DetailsGroup
* group
= GroupForSection(section
);
2427 for (DetailInputs::const_iterator iter
= inputs
.begin();
2428 iter
!= inputs
.end(); ++iter
) {
2429 const DetailInput
& input
= *iter
;
2430 bool editable
= delegate_
->InputIsEditable(input
, section
);
2432 TextfieldMap::iterator text_mapping
= group
->textfields
.find(input
.type
);
2433 if (text_mapping
!= group
->textfields
.end()) {
2434 DecoratedTextfield
* decorated
= text_mapping
->second
;
2435 decorated
->SetEditable(editable
);
2439 ComboboxMap::iterator combo_mapping
= group
->comboboxes
.find(input
.type
);
2440 if (combo_mapping
!= group
->comboboxes
.end()) {
2441 views::Combobox
* combobox
= combo_mapping
->second
;
2442 combobox
->SetEnabled(editable
);
2447 AutofillDialogViews::DetailsGroup::DetailsGroup(DialogSection section
)
2451 suggested_info(NULL
),
2452 suggested_button(NULL
) {}
2454 AutofillDialogViews::DetailsGroup::~DetailsGroup() {}
2456 } // namespace autofill