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
{
169 set_border(views::Border::CreateEmptyBorder(10, 0, 0, 0));
172 virtual ~SectionRowView() {}
174 // views::View implementation:
175 virtual gfx::Size
GetPreferredSize() OVERRIDE
{
178 for (int i
= 0; i
< child_count(); ++i
) {
179 if (child_at(i
)->visible()) {
181 width
+= kAroundTextPadding
;
183 gfx::Size size
= child_at(i
)->GetPreferredSize();
184 height
= std::max(height
, size
.height());
185 width
+= size
.width();
189 gfx::Insets insets
= GetInsets();
190 return gfx::Size(width
+ insets
.width(), height
+ insets
.height());
193 virtual void Layout() OVERRIDE
{
194 const gfx::Rect bounds
= GetContentsBounds();
196 // Icon is left aligned.
197 int start_x
= bounds
.x();
198 views::View
* icon
= child_at(0);
199 if (icon
->visible()) {
200 icon
->SizeToPreferredSize();
202 icon
->SetY(bounds
.y() +
203 (bounds
.height() - icon
->bounds().height()) / 2);
204 start_x
+= icon
->bounds().width() + kAroundTextPadding
;
207 // Textfield is right aligned.
208 int end_x
= bounds
.width();
209 views::View
* decorated
= child_at(2);
210 if (decorated
->visible()) {
211 const int preferred_width
= decorated
->GetPreferredSize().width();
212 decorated
->SetBounds(bounds
.width() - preferred_width
, bounds
.y(),
213 preferred_width
, bounds
.height());
214 end_x
= decorated
->bounds().x() - kAroundTextPadding
;
217 // Label takes up all the space in between.
218 views::View
* label
= child_at(1);
219 if (label
->visible())
220 label
->SetBounds(start_x
, bounds
.y(), end_x
- start_x
, bounds
.height());
222 views::View::Layout();
226 DISALLOW_COPY_AND_ASSIGN(SectionRowView
);
229 // A view that propagates visibility and preferred size changes.
230 class LayoutPropagationView
: public views::View
{
232 LayoutPropagationView() {}
233 virtual ~LayoutPropagationView() {}
236 virtual void ChildVisibilityChanged(views::View
* child
) OVERRIDE
{
237 PreferredSizeChanged();
239 virtual void ChildPreferredSizeChanged(views::View
* child
) OVERRIDE
{
240 PreferredSizeChanged();
244 DISALLOW_COPY_AND_ASSIGN(LayoutPropagationView
);
247 // A View for a single notification banner.
248 class NotificationView
: public views::View
,
249 public views::ButtonListener
,
250 public views::StyledLabelListener
{
252 NotificationView(const DialogNotification
& data
,
253 AutofillDialogViewDelegate
* delegate
)
257 scoped_ptr
<views::View
> label_view
;
258 if (data
.HasCheckbox()) {
259 scoped_ptr
<views::Checkbox
> checkbox(
260 new views::Checkbox(base::string16()));
261 checkbox
->SetText(data
.display_text());
262 checkbox
->SetTextMultiLine(true);
263 checkbox
->SetHorizontalAlignment(gfx::ALIGN_LEFT
);
264 checkbox
->SetTextColor(views::Button::STATE_NORMAL
,
265 data
.GetTextColor());
266 checkbox
->SetTextColor(views::Button::STATE_HOVERED
,
267 data
.GetTextColor());
268 checkbox
->SetChecked(data
.checked());
269 checkbox
->set_listener(this);
270 checkbox_
= checkbox
.get();
271 label_view
.reset(checkbox
.release());
273 scoped_ptr
<views::StyledLabel
> label(new views::StyledLabel(
274 data
.display_text(), this));
275 label
->set_auto_color_readability_enabled(false);
277 views::StyledLabel::RangeStyleInfo text_style
;
278 text_style
.color
= data
.GetTextColor();
280 if (data
.link_range().is_empty()) {
281 label
->AddStyleRange(gfx::Range(0, data
.display_text().size()),
284 gfx::Range
prefix_range(0, data
.link_range().start());
285 if (!prefix_range
.is_empty())
286 label
->AddStyleRange(prefix_range
, text_style
);
288 label
->AddStyleRange(
290 views::StyledLabel::RangeStyleInfo::CreateForLink());
292 gfx::Range
suffix_range(data
.link_range().end(),
293 data
.display_text().size());
294 if (!suffix_range
.is_empty())
295 label
->AddStyleRange(suffix_range
, text_style
);
298 label_view
.reset(label
.release());
301 AddChildView(label_view
.release());
303 if (!data
.tooltip_text().empty())
304 AddChildView(new TooltipIcon(data
.tooltip_text()));
307 views::Background::CreateSolidBackground(data
.GetBackgroundColor()));
308 set_border(views::Border::CreateSolidSidedBorder(1, 0, 1, 0,
309 data
.GetBorderColor()));
312 virtual ~NotificationView() {}
314 views::Checkbox
* checkbox() {
318 // views::View implementation.
319 virtual gfx::Insets
GetInsets() const OVERRIDE
{
320 int vertical_padding
= kNotificationPadding
;
322 vertical_padding
-= 3;
323 return gfx::Insets(vertical_padding
, kDialogEdgePadding
,
324 vertical_padding
, kDialogEdgePadding
);
327 virtual int GetHeightForWidth(int width
) OVERRIDE
{
328 int label_width
= width
- GetInsets().width();
329 if (child_count() > 1) {
330 views::View
* tooltip_icon
= child_at(1);
331 label_width
-= tooltip_icon
->GetPreferredSize().width() +
335 return child_at(0)->GetHeightForWidth(label_width
) + GetInsets().height();
338 virtual void Layout() OVERRIDE
{
339 // Surprisingly, GetContentsBounds() doesn't consult GetInsets().
340 gfx::Rect bounds
= GetLocalBounds();
341 bounds
.Inset(GetInsets());
342 int right_bound
= bounds
.right();
344 if (child_count() > 1) {
345 // The icon takes up the entire vertical space and an extra 20px on
346 // each side. This increases the hover target for the tooltip.
347 views::View
* tooltip_icon
= child_at(1);
348 gfx::Size icon_size
= tooltip_icon
->GetPreferredSize();
349 int icon_width
= icon_size
.width() + kDialogEdgePadding
;
350 right_bound
-= icon_width
;
351 tooltip_icon
->SetBounds(
353 icon_width
+ kDialogEdgePadding
, GetLocalBounds().height());
356 child_at(0)->SetBounds(bounds
.x(), bounds
.y(),
357 right_bound
- bounds
.x(), bounds
.height());
360 // views::ButtonListener implementation.
361 virtual void ButtonPressed(views::Button
* sender
,
362 const ui::Event
& event
) OVERRIDE
{
363 DCHECK_EQ(sender
, checkbox_
);
364 delegate_
->NotificationCheckboxStateChanged(data_
.type(),
365 checkbox_
->checked());
368 // views::StyledLabelListener implementation.
369 virtual void StyledLabelLinkClicked(const gfx::Range
& range
, int event_flags
)
371 delegate_
->LinkClicked(data_
.link_url());
375 // The model data for this notification.
376 DialogNotification data_
;
378 // The delegate that handles interaction with |this|.
379 AutofillDialogViewDelegate
* delegate_
;
381 // The checkbox associated with this notification, or NULL if there is none.
382 views::Checkbox
* checkbox_
;
384 DISALLOW_COPY_AND_ASSIGN(NotificationView
);
387 // A view that displays a loading message with some dancing dots.
388 class LoadingAnimationView
: public views::View
,
389 public gfx::AnimationDelegate
{
391 explicit LoadingAnimationView(const base::string16
& text
) :
392 container_(new views::View()) {
393 AddChildView(container_
);
394 container_
->SetLayoutManager(
395 new views::BoxLayout(views::BoxLayout::kHorizontal
, 0, 0, 0));
397 const gfx::FontList
& font_list
=
398 ui::ResourceBundle::GetSharedInstance().GetFontList(
399 ui::ResourceBundle::LargeFont
);
400 animation_
.reset(new LoadingAnimation(this, font_list
.GetHeight()));
402 container_
->AddChildView(new views::Label(text
, font_list
));
404 for (size_t i
= 0; i
< 3; ++i
) {
405 container_
->AddChildView(
406 new views::Label(base::ASCIIToUTF16("."), font_list
));
409 OnNativeThemeChanged(GetNativeTheme());
412 virtual ~LoadingAnimationView() {}
414 // views::View implementation.
415 virtual void SetVisible(bool visible
) OVERRIDE
{
421 views::View::SetVisible(visible
);
424 virtual void Layout() OVERRIDE
{
425 gfx::Size container_size
= container_
->GetPreferredSize();
426 gfx::Rect
container_bounds((width() - container_size
.width()) / 2,
427 (height() - container_size
.height()) / 2,
428 container_size
.width(),
429 container_size
.height());
430 container_
->SetBoundsRect(container_bounds
);
431 container_
->Layout();
433 for (size_t i
= 0; i
< 3; ++i
) {
434 views::View
* dot
= container_
->child_at(i
+ 1);
435 dot
->SetY(dot
->y() + animation_
->GetCurrentValueForDot(i
));
439 virtual void OnNativeThemeChanged(const ui::NativeTheme
* theme
) OVERRIDE
{
440 set_background(views::Background::CreateSolidBackground(
441 theme
->GetSystemColor(ui::NativeTheme::kColorId_DialogBackground
)));
444 // gfx::AnimationDelegate implementation.
445 virtual void AnimationProgressed(const gfx::Animation
* animation
) OVERRIDE
{
446 DCHECK_EQ(animation
, animation_
.get());
451 // Contains the "Loading" label and the dots.
452 views::View
* container_
;
454 scoped_ptr
<LoadingAnimation
> animation_
;
456 DISALLOW_COPY_AND_ASSIGN(LoadingAnimationView
);
461 // AutofillDialogViews::AccountChooser -----------------------------------------
463 AutofillDialogViews::AccountChooser::AccountChooser(
464 AutofillDialogViewDelegate
* delegate
)
465 : image_(new views::ImageView()),
466 menu_button_(new views::MenuButton(NULL
, base::string16(), this, true)),
467 link_(new views::Link()),
468 delegate_(delegate
) {
469 set_border(views::Border::CreateEmptyBorder(0, 0, 0, 10));
471 new views::BoxLayout(views::BoxLayout::kHorizontal
, 0, 0,
472 kAroundTextPadding
));
473 AddChildView(image_
);
475 menu_button_
->set_background(NULL
);
476 menu_button_
->set_border(NULL
);
477 gfx::Insets insets
= GetInsets();
478 menu_button_
->SetFocusPainter(
479 views::Painter::CreateDashedFocusPainterWithInsets(insets
));
480 menu_button_
->SetFocusable(true);
481 AddChildView(menu_button_
);
483 link_
->set_listener(this);
487 AutofillDialogViews::AccountChooser::~AccountChooser() {}
489 void AutofillDialogViews::AccountChooser::Update() {
490 SetVisible(delegate_
->ShouldShowAccountChooser());
492 gfx::Image icon
= delegate_
->AccountChooserImage();
493 image_
->SetImage(icon
.AsImageSkia());
494 menu_button_
->SetText(delegate_
->AccountChooserText());
495 // This allows the button to shrink if the new text is smaller.
496 menu_button_
->ClearMaxTextSize();
498 bool show_link
= !delegate_
->MenuModelForAccountChooser();
499 menu_button_
->SetVisible(!show_link
);
500 link_
->SetText(delegate_
->SignInLinkText());
501 link_
->SetVisible(show_link
);
503 menu_runner_
.reset();
505 PreferredSizeChanged();
508 void AutofillDialogViews::AccountChooser::OnMenuButtonClicked(
510 const gfx::Point
& point
) {
511 DCHECK_EQ(menu_button_
, source
);
513 ui::MenuModel
* model
= delegate_
->MenuModelForAccountChooser();
517 menu_runner_
.reset(new views::MenuRunner(model
));
518 if (menu_runner_
->RunMenuAt(source
->GetWidget(),
520 source
->GetBoundsInScreen(),
521 views::MenuItemView::TOPRIGHT
,
522 ui::MENU_SOURCE_NONE
,
523 0) == views::MenuRunner::MENU_DELETED
) {
528 views::View
* AutofillDialogViews::GetLoadingShieldForTesting() {
529 return loading_shield_
;
532 views::WebView
* AutofillDialogViews::GetSignInWebViewForTesting() {
533 return sign_in_web_view_
;
536 views::View
* AutofillDialogViews::GetNotificationAreaForTesting() {
537 return notification_area_
;
540 views::View
* AutofillDialogViews::GetScrollableAreaForTesting() {
541 return scrollable_area_
;
544 void AutofillDialogViews::AccountChooser::LinkClicked(views::Link
* source
,
546 delegate_
->SignInLinkClicked();
549 // AutofillDialogViews::OverlayView --------------------------------------------
551 AutofillDialogViews::OverlayView::OverlayView(
552 AutofillDialogViewDelegate
* delegate
)
553 : delegate_(delegate
),
554 image_view_(new views::ImageView()),
555 message_view_(new views::Label()) {
556 message_view_
->SetAutoColorReadabilityEnabled(false);
557 message_view_
->SetMultiLine(true);
559 AddChildView(image_view_
);
560 AddChildView(message_view_
);
562 OnNativeThemeChanged(GetNativeTheme());
565 AutofillDialogViews::OverlayView::~OverlayView() {}
567 int AutofillDialogViews::OverlayView::GetHeightForContentsForWidth(int width
) {
568 // In this case, 0 means "no preference".
569 if (!message_view_
->visible())
572 return kOverlayImageBottomMargin
+
573 views::kButtonVEdgeMarginNew
+
574 message_view_
->GetHeightForWidth(width
) +
575 image_view_
->GetHeightForWidth(width
);
578 void AutofillDialogViews::OverlayView::UpdateState() {
579 const DialogOverlayState
& state
= delegate_
->GetDialogOverlay();
581 if (state
.image
.IsEmpty()) {
586 image_view_
->SetImage(state
.image
.ToImageSkia());
588 message_view_
->SetVisible(!state
.string
.text
.empty());
589 message_view_
->SetText(state
.string
.text
);
590 message_view_
->SetFontList(gfx::FontList(state
.string
.font
));
591 message_view_
->SetEnabledColor(GetNativeTheme()->GetSystemColor(
592 ui::NativeTheme::kColorId_TextfieldReadOnlyColor
));
594 message_view_
->set_border(
595 views::Border::CreateEmptyBorder(kOverlayMessageVerticalPadding
,
597 kOverlayMessageVerticalPadding
,
598 kDialogEdgePadding
));
603 gfx::Insets
AutofillDialogViews::OverlayView::GetInsets() const {
604 return gfx::Insets(12, 12, 12, 12);
607 void AutofillDialogViews::OverlayView::Layout() {
608 gfx::Rect bounds
= ContentBoundsSansBubbleBorder();
609 if (!message_view_
->visible()) {
610 image_view_
->SetBoundsRect(bounds
);
614 int message_height
= message_view_
->GetHeightForWidth(bounds
.width());
615 int y
= bounds
.bottom() - message_height
;
616 message_view_
->SetBounds(bounds
.x(), y
, bounds
.width(), message_height
);
618 gfx::Size image_size
= image_view_
->GetPreferredSize();
619 y
-= image_size
.height() + kOverlayImageBottomMargin
;
620 image_view_
->SetBounds(bounds
.x(), y
, bounds
.width(), image_size
.height());
623 const char* AutofillDialogViews::OverlayView::GetClassName() const {
624 return kOverlayViewClassName
;
627 void AutofillDialogViews::OverlayView::OnPaint(gfx::Canvas
* canvas
) {
628 // BubbleFrameView doesn't mask the window, it just draws the border via
629 // image assets. Match that rounding here.
630 gfx::Rect rect
= ContentBoundsSansBubbleBorder();
631 const SkScalar kCornerRadius
= SkIntToScalar(
632 GetBubbleBorder() ? GetBubbleBorder()->GetBorderCornerRadius() : 2);
633 gfx::Path window_mask
;
634 window_mask
.addRoundRect(gfx::RectToSkRect(rect
),
635 kCornerRadius
, kCornerRadius
);
636 canvas
->ClipPath(window_mask
);
638 OnPaintBackground(canvas
);
640 // Draw the arrow, border, and fill for the bottom area.
641 if (message_view_
->visible()) {
642 const int arrow_half_width
= kArrowWidth
/ 2.0f
;
644 int y
= message_view_
->y() - 1;
645 // Note that we purposely draw slightly outside of |rect| so that the
646 // stroke is hidden on the sides.
647 arrow
.moveTo(rect
.x() - 1, y
);
648 arrow
.rLineTo(rect
.width() / 2 - arrow_half_width
, 0);
649 arrow
.rLineTo(arrow_half_width
, -kArrowHeight
);
650 arrow
.rLineTo(arrow_half_width
, kArrowHeight
);
651 arrow
.lineTo(rect
.right() + 1, y
);
652 arrow
.lineTo(rect
.right() + 1, rect
.bottom() + 1);
653 arrow
.lineTo(rect
.x() - 1, rect
.bottom() + 1);
656 // The mocked alpha blends were 7 for background & 10 for the border against
657 // a very bright background. The eye perceives luminance differences of
658 // darker colors much less than lighter colors, so increase the alpha blend
659 // amount the darker the color (lower the luminance).
661 SkColor background_color
= background()->get_color();
662 int background_luminance
=
663 color_utils::GetLuminanceForColor(background_color
);
664 int background_alpha
= static_cast<int>(
665 7 + 15 * (255 - background_luminance
) / 255);
666 int subtle_border_alpha
= static_cast<int>(
667 10 + 20 * (255 - background_luminance
) / 255);
669 paint
.setColor(color_utils::BlendTowardOppositeLuminance(
670 background_color
, background_alpha
));
671 paint
.setStyle(SkPaint::kFill_Style
);
672 canvas
->DrawPath(arrow
, paint
);
673 paint
.setColor(color_utils::BlendTowardOppositeLuminance(
674 background_color
, subtle_border_alpha
));
675 paint
.setStyle(SkPaint::kStroke_Style
);
676 canvas
->DrawPath(arrow
, paint
);
679 PaintChildren(canvas
);
682 void AutofillDialogViews::OverlayView::OnNativeThemeChanged(
683 const ui::NativeTheme
* theme
) {
684 set_background(views::Background::CreateSolidBackground(
685 theme
->GetSystemColor(ui::NativeTheme::kColorId_DialogBackground
)));
688 views::BubbleBorder
* AutofillDialogViews::OverlayView::GetBubbleBorder() {
689 views::View
* frame
= GetWidget()->non_client_view()->frame_view();
690 std::string
bubble_frame_view_name(views::BubbleFrameView::kViewClassName
);
691 if (frame
->GetClassName() == bubble_frame_view_name
)
692 return static_cast<views::BubbleFrameView
*>(frame
)->bubble_border();
697 gfx::Rect
AutofillDialogViews::OverlayView::ContentBoundsSansBubbleBorder() {
698 gfx::Rect bounds
= GetContentsBounds();
699 int bubble_width
= 5;
700 if (GetBubbleBorder())
701 bubble_width
= GetBubbleBorder()->GetBorderThickness();
702 bounds
.Inset(bubble_width
, bubble_width
, bubble_width
, bubble_width
);
706 // AutofillDialogViews::NotificationArea ---------------------------------------
708 AutofillDialogViews::NotificationArea::NotificationArea(
709 AutofillDialogViewDelegate
* delegate
)
710 : delegate_(delegate
) {
711 // Reserve vertical space for the arrow (regardless of whether one exists).
712 // The -1 accounts for the border.
713 set_border(views::Border::CreateEmptyBorder(kArrowHeight
- 1, 0, 0, 0));
715 views::BoxLayout
* box_layout
=
716 new views::BoxLayout(views::BoxLayout::kVertical
, 0, 0, 0);
717 SetLayoutManager(box_layout
);
720 AutofillDialogViews::NotificationArea::~NotificationArea() {}
722 void AutofillDialogViews::NotificationArea::SetNotifications(
723 const std::vector
<DialogNotification
>& notifications
) {
724 notifications_
= notifications
;
726 RemoveAllChildViews(true);
728 if (notifications_
.empty())
731 for (size_t i
= 0; i
< notifications_
.size(); ++i
) {
732 const DialogNotification
& notification
= notifications_
[i
];
733 scoped_ptr
<NotificationView
> view(new NotificationView(notification
,
736 AddChildView(view
.release());
739 PreferredSizeChanged();
742 gfx::Size
AutofillDialogViews::NotificationArea::GetPreferredSize() {
743 gfx::Size size
= views::View::GetPreferredSize();
744 // Ensure that long notifications wrap and don't enlarge the dialog.
749 const char* AutofillDialogViews::NotificationArea::GetClassName() const {
750 return kNotificationAreaClassName
;
753 void AutofillDialogViews::NotificationArea::PaintChildren(
754 gfx::Canvas
* canvas
) {}
756 void AutofillDialogViews::NotificationArea::OnPaint(gfx::Canvas
* canvas
) {
757 views::View::OnPaint(canvas
);
758 views::View::PaintChildren(canvas
);
763 GetMirroredXInView(width() - arrow_centering_anchor_
->width() / 2.0f
),
764 notifications_
[0].GetBackgroundColor(),
765 notifications_
[0].GetBorderColor());
769 void AutofillDialogViews::OnWidgetClosing(views::Widget
* widget
) {
770 observer_
.Remove(widget
);
771 if (error_bubble_
&& error_bubble_
->GetWidget() == widget
)
772 error_bubble_
= NULL
;
775 void AutofillDialogViews::OnWidgetBoundsChanged(views::Widget
* widget
,
776 const gfx::Rect
& new_bounds
) {
777 // Notify the web contents of its new auto-resize limits.
778 if (sign_in_delegate_
&& sign_in_web_view_
->visible()) {
779 sign_in_delegate_
->UpdateLimitsAndEnableAutoResize(
780 GetMinimumSignInViewSize(), GetMaximumSignInViewSize());
785 bool AutofillDialogViews::NotificationArea::HasArrow() {
786 return !notifications_
.empty() && notifications_
[0].HasArrow() &&
787 arrow_centering_anchor_
.get();
790 // AutofillDialogViews::SectionContainer ---------------------------------------
792 AutofillDialogViews::SectionContainer::SectionContainer(
793 const base::string16
& label
,
794 views::View
* controls
,
795 views::Button
* proxy_button
)
796 : proxy_button_(proxy_button
),
797 forward_mouse_events_(false) {
798 set_notify_enter_exit_on_child(true);
800 set_border(views::Border::CreateEmptyBorder(kDetailSectionVerticalPadding
,
802 kDetailSectionVerticalPadding
,
803 kDialogEdgePadding
));
805 ui::ResourceBundle
& rb
= ui::ResourceBundle::GetSharedInstance();
806 views::Label
* label_view
= new views::Label(
807 label
, rb
.GetFontList(ui::ResourceBundle::BoldFont
));
808 label_view
->SetHorizontalAlignment(gfx::ALIGN_LEFT
);
810 views::View
* label_bar
= new views::View();
811 views::GridLayout
* label_bar_layout
= new views::GridLayout(label_bar
);
812 label_bar
->SetLayoutManager(label_bar_layout
);
813 const int kColumnSetId
= 0;
814 views::ColumnSet
* columns
= label_bar_layout
->AddColumnSet(kColumnSetId
);
816 views::GridLayout::LEADING
,
817 views::GridLayout::LEADING
,
819 views::GridLayout::FIXED
,
820 kSectionContainerWidth
- proxy_button
->GetPreferredSize().width(),
822 columns
->AddColumn(views::GridLayout::LEADING
,
823 views::GridLayout::LEADING
,
825 views::GridLayout::USE_PREF
,
828 label_bar_layout
->StartRow(0, kColumnSetId
);
829 label_bar_layout
->AddView(label_view
);
830 label_bar_layout
->AddView(proxy_button
);
832 SetLayoutManager(new views::BoxLayout(views::BoxLayout::kVertical
, 0, 0, 0));
833 AddChildView(label_bar
);
834 AddChildView(controls
);
837 AutofillDialogViews::SectionContainer::~SectionContainer() {}
839 void AutofillDialogViews::SectionContainer::SetActive(bool active
) {
840 bool is_active
= active
&& proxy_button_
->visible();
841 if (is_active
== !!background())
844 set_background(is_active
?
845 views::Background::CreateSolidBackground(kShadingColor
) :
850 void AutofillDialogViews::SectionContainer::SetForwardMouseEvents(
852 forward_mouse_events_
= forward
;
854 set_background(NULL
);
857 const char* AutofillDialogViews::SectionContainer::GetClassName() const {
858 return kSectionContainerClassName
;
861 void AutofillDialogViews::SectionContainer::OnMouseMoved(
862 const ui::MouseEvent
& event
) {
863 SetActive(ShouldForwardEvent(event
));
866 void AutofillDialogViews::SectionContainer::OnMouseEntered(
867 const ui::MouseEvent
& event
) {
868 if (!ShouldForwardEvent(event
))
872 proxy_button_
->OnMouseEntered(ProxyEvent(event
));
876 void AutofillDialogViews::SectionContainer::OnMouseExited(
877 const ui::MouseEvent
& event
) {
879 if (!ShouldForwardEvent(event
))
882 proxy_button_
->OnMouseExited(ProxyEvent(event
));
886 bool AutofillDialogViews::SectionContainer::OnMousePressed(
887 const ui::MouseEvent
& event
) {
888 if (!ShouldForwardEvent(event
))
891 return proxy_button_
->OnMousePressed(ProxyEvent(event
));
894 void AutofillDialogViews::SectionContainer::OnMouseReleased(
895 const ui::MouseEvent
& event
) {
896 if (!ShouldForwardEvent(event
))
899 proxy_button_
->OnMouseReleased(ProxyEvent(event
));
902 void AutofillDialogViews::SectionContainer::OnGestureEvent(
903 ui::GestureEvent
* event
) {
904 if (!ShouldForwardEvent(*event
))
907 proxy_button_
->OnGestureEvent(event
);
910 views::View
* AutofillDialogViews::SectionContainer::GetEventHandlerForRect(
911 const gfx::Rect
& rect
) {
912 // TODO(tdanderson): Modify this function to support rect-based event
915 views::View
* handler
= views::View::GetEventHandlerForRect(rect
);
916 // If the event is not in the label bar and there's no background to be
917 // cleared, let normal event handling take place.
919 rect
.CenterPoint().y() > child_at(0)->bounds().bottom()) {
923 // Special case for (CVC) inputs in the suggestion view.
924 if (forward_mouse_events_
&&
925 handler
->GetAncestorWithClassName(DecoratedTextfield::kViewClassName
)) {
929 // Special case for the proxy button itself.
930 if (handler
== proxy_button_
)
937 ui::MouseEvent
AutofillDialogViews::SectionContainer::ProxyEvent(
938 const ui::MouseEvent
& event
) {
939 ui::MouseEvent event_copy
= event
;
940 event_copy
.set_location(gfx::Point());
944 bool AutofillDialogViews::SectionContainer::ShouldForwardEvent(
945 const ui::LocatedEvent
& event
) {
946 // Always forward events on the label bar.
947 return forward_mouse_events_
|| event
.y() <= child_at(0)->bounds().bottom();
950 // AutofillDialogViews::SuggestedButton ----------------------------------------
952 AutofillDialogViews::SuggestedButton::SuggestedButton(
953 views::MenuButtonListener
* listener
)
954 : views::MenuButton(NULL
, base::string16(), listener
, false) {
955 const int kFocusBorderWidth
= 1;
956 set_border(views::Border::CreateEmptyBorder(kMenuButtonTopInset
,
958 kMenuButtonBottomInset
,
960 gfx::Insets insets
= GetInsets();
961 insets
+= gfx::Insets(-kFocusBorderWidth
, -kFocusBorderWidth
,
962 -kFocusBorderWidth
, -kFocusBorderWidth
);
964 views::Painter::CreateDashedFocusPainterWithInsets(insets
));
968 AutofillDialogViews::SuggestedButton::~SuggestedButton() {}
970 gfx::Size
AutofillDialogViews::SuggestedButton::GetPreferredSize() {
971 ui::ResourceBundle
& rb
= ui::ResourceBundle::GetSharedInstance();
972 gfx::Size size
= rb
.GetImageNamed(ResourceIDForState()).Size();
973 const gfx::Insets insets
= GetInsets();
974 size
.Enlarge(insets
.width(), insets
.height());
978 const char* AutofillDialogViews::SuggestedButton::GetClassName() const {
979 return kSuggestedButtonClassName
;
982 void AutofillDialogViews::SuggestedButton::PaintChildren(gfx::Canvas
* canvas
) {}
984 void AutofillDialogViews::SuggestedButton::OnPaint(gfx::Canvas
* canvas
) {
985 ui::ResourceBundle
& rb
= ui::ResourceBundle::GetSharedInstance();
986 const gfx::Insets insets
= GetInsets();
987 canvas
->DrawImageInt(*rb
.GetImageSkiaNamed(ResourceIDForState()),
988 insets
.left(), insets
.top());
989 views::Painter::PaintFocusPainter(this, canvas
, focus_painter());
992 int AutofillDialogViews::SuggestedButton::ResourceIDForState() const {
993 views::Button::ButtonState button_state
= state();
994 if (button_state
== views::Button::STATE_PRESSED
)
995 return IDR_AUTOFILL_DIALOG_MENU_BUTTON_P
;
996 else if (button_state
== views::Button::STATE_HOVERED
)
997 return IDR_AUTOFILL_DIALOG_MENU_BUTTON_H
;
998 else if (button_state
== views::Button::STATE_DISABLED
)
999 return IDR_AUTOFILL_DIALOG_MENU_BUTTON_D
;
1000 DCHECK_EQ(views::Button::STATE_NORMAL
, button_state
);
1001 return IDR_AUTOFILL_DIALOG_MENU_BUTTON
;
1004 // AutofillDialogViews::DetailsContainerView -----------------------------------
1006 AutofillDialogViews::DetailsContainerView::DetailsContainerView(
1007 const base::Closure
& callback
)
1008 : bounds_changed_callback_(callback
),
1009 ignore_layouts_(false) {}
1011 AutofillDialogViews::DetailsContainerView::~DetailsContainerView() {}
1013 void AutofillDialogViews::DetailsContainerView::OnBoundsChanged(
1014 const gfx::Rect
& previous_bounds
) {
1015 bounds_changed_callback_
.Run();
1018 void AutofillDialogViews::DetailsContainerView::Layout() {
1019 if (!ignore_layouts_
)
1020 views::View::Layout();
1023 // AutofillDialogViews::SuggestionView -----------------------------------------
1025 AutofillDialogViews::SuggestionView::SuggestionView(
1026 AutofillDialogViews
* autofill_dialog
)
1027 : label_(new views::Label()),
1028 label_line_2_(new views::Label()),
1029 icon_(new views::ImageView()),
1031 new DecoratedTextfield(base::string16(),
1034 // TODO(estade): Make this the correct color.
1036 views::Border::CreateSolidSidedBorder(1, 0, 0, 0, SK_ColorLTGRAY
));
1038 SectionRowView
* label_container
= new SectionRowView();
1039 AddChildView(label_container
);
1042 label_container
->AddChildView(icon_
);
1043 label_
->SetHorizontalAlignment(gfx::ALIGN_LEFT
);
1044 label_container
->AddChildView(label_
);
1046 // TODO(estade): get the sizing and spacing right on this textfield.
1047 decorated_
->SetVisible(false);
1048 decorated_
->set_default_width_in_chars(15);
1049 label_container
->AddChildView(decorated_
);
1051 label_line_2_
->SetHorizontalAlignment(gfx::ALIGN_LEFT
);
1052 label_line_2_
->SetVisible(false);
1053 label_line_2_
->SetLineHeight(22);
1054 label_line_2_
->SetMultiLine(true);
1055 AddChildView(label_line_2_
);
1057 SetLayoutManager(new views::BoxLayout(views::BoxLayout::kVertical
, 0, 0, 7));
1060 AutofillDialogViews::SuggestionView::~SuggestionView() {}
1062 gfx::Size
AutofillDialogViews::SuggestionView::GetPreferredSize() {
1063 // There's no preferred width. The parent's layout should get the preferred
1064 // height from GetHeightForWidth().
1068 int AutofillDialogViews::SuggestionView::GetHeightForWidth(int width
) {
1070 CanUseVerticallyCompactText(width
, &height
);
1074 bool AutofillDialogViews::SuggestionView::CanUseVerticallyCompactText(
1075 int available_width
,
1076 int* resulting_height
) {
1077 // This calculation may be costly, avoid doing it more than once per width.
1078 if (!calculated_heights_
.count(available_width
)) {
1079 // Changing the state of |this| now will lead to extra layouts and
1080 // paints we don't want, so create another SuggestionView to calculate
1081 // which label we have room to show.
1082 SuggestionView
sizing_view(NULL
);
1083 sizing_view
.SetLabelText(state_
.vertically_compact_text
);
1084 sizing_view
.SetIcon(state_
.icon
);
1085 sizing_view
.SetTextfield(state_
.extra_text
, state_
.extra_icon
);
1086 sizing_view
.label_
->SetSize(gfx::Size(available_width
, 0));
1087 sizing_view
.label_line_2_
->SetSize(gfx::Size(available_width
, 0));
1089 // Shortcut |sizing_view|'s GetHeightForWidth() to avoid an infinite loop.
1090 // Its BoxLayout must do these calculations for us.
1091 views::LayoutManager
* layout
= sizing_view
.GetLayoutManager();
1092 if (layout
->GetPreferredSize(&sizing_view
).width() <= available_width
) {
1093 calculated_heights_
[available_width
] = std::make_pair(
1095 layout
->GetPreferredHeightForWidth(&sizing_view
, available_width
));
1097 sizing_view
.SetLabelText(state_
.horizontally_compact_text
);
1098 calculated_heights_
[available_width
] = std::make_pair(
1100 layout
->GetPreferredHeightForWidth(&sizing_view
, available_width
));
1104 const std::pair
<bool, int>& values
= calculated_heights_
[available_width
];
1105 *resulting_height
= values
.second
;
1106 return values
.first
;
1109 void AutofillDialogViews::SuggestionView::OnBoundsChanged(
1110 const gfx::Rect
& previous_bounds
) {
1114 void AutofillDialogViews::SuggestionView::SetState(
1115 const SuggestionState
& state
) {
1116 calculated_heights_
.clear();
1118 SetVisible(state_
.visible
);
1120 SetIcon(state_
.icon
);
1121 SetTextfield(state_
.extra_text
, state_
.extra_icon
);
1122 PreferredSizeChanged();
1125 void AutofillDialogViews::SuggestionView::SetLabelText(
1126 const base::string16
& text
) {
1127 // TODO(estade): does this localize well?
1128 base::string16
line_return(base::ASCIIToUTF16("\n"));
1129 size_t position
= text
.find(line_return
);
1130 if (position
== base::string16::npos
) {
1131 label_
->SetText(text
);
1132 label_line_2_
->SetVisible(false);
1134 label_
->SetText(text
.substr(0, position
));
1135 label_line_2_
->SetText(text
.substr(position
+ line_return
.length()));
1136 label_line_2_
->SetVisible(true);
1140 void AutofillDialogViews::SuggestionView::SetIcon(
1141 const gfx::Image
& image
) {
1142 icon_
->SetVisible(!image
.IsEmpty());
1143 icon_
->SetImage(image
.AsImageSkia());
1146 void AutofillDialogViews::SuggestionView::SetTextfield(
1147 const base::string16
& placeholder_text
,
1148 const gfx::Image
& icon
) {
1149 decorated_
->set_placeholder_text(placeholder_text
);
1150 decorated_
->SetIcon(icon
);
1151 decorated_
->SetVisible(!placeholder_text
.empty());
1154 void AutofillDialogViews::SuggestionView::UpdateLabelText() {
1156 SetLabelText(CanUseVerticallyCompactText(width(), &unused
) ?
1157 state_
.vertically_compact_text
:
1158 state_
.horizontally_compact_text
);
1161 // AutofillDialogView ----------------------------------------------------------
1164 AutofillDialogView
* AutofillDialogView::Create(
1165 AutofillDialogViewDelegate
* delegate
) {
1166 return new AutofillDialogViews(delegate
);
1169 // AutofillDialogViews ---------------------------------------------------------
1171 AutofillDialogViews::AutofillDialogViews(AutofillDialogViewDelegate
* delegate
)
1172 : delegate_(delegate
),
1174 needs_update_(false),
1176 notification_area_(NULL
),
1177 account_chooser_(NULL
),
1178 sign_in_web_view_(NULL
),
1179 scrollable_area_(NULL
),
1180 details_container_(NULL
),
1181 loading_shield_(NULL
),
1182 loading_shield_height_(0),
1183 overlay_view_(NULL
),
1184 button_strip_extra_view_(NULL
),
1185 save_in_chrome_checkbox_(NULL
),
1186 save_in_chrome_checkbox_container_(NULL
),
1187 button_strip_image_(NULL
),
1188 footnote_view_(NULL
),
1189 legal_document_view_(NULL
),
1190 focus_manager_(NULL
),
1191 error_bubble_(NULL
),
1194 detail_groups_
.insert(std::make_pair(SECTION_CC
,
1195 DetailsGroup(SECTION_CC
)));
1196 detail_groups_
.insert(std::make_pair(SECTION_BILLING
,
1197 DetailsGroup(SECTION_BILLING
)));
1198 detail_groups_
.insert(std::make_pair(SECTION_CC_BILLING
,
1199 DetailsGroup(SECTION_CC_BILLING
)));
1200 detail_groups_
.insert(std::make_pair(SECTION_SHIPPING
,
1201 DetailsGroup(SECTION_SHIPPING
)));
1204 AutofillDialogViews::~AutofillDialogViews() {
1209 void AutofillDialogViews::Show() {
1211 UpdateAccountChooser();
1212 UpdateNotificationArea();
1213 UpdateButtonStripExtraView();
1215 // Ownership of |contents_| is handed off by this call. The widget will take
1216 // care of deleting itself after calling DeleteDelegate().
1217 WebContentsModalDialogManager
* web_contents_modal_dialog_manager
=
1218 WebContentsModalDialogManager::FromWebContents(
1219 delegate_
->GetWebContents());
1220 WebContentsModalDialogManagerDelegate
* modal_delegate
=
1221 web_contents_modal_dialog_manager
->delegate();
1222 DCHECK(modal_delegate
);
1223 window_
= views::Widget::CreateWindowAsFramelessChild(
1224 this, modal_delegate
->GetWebContentsModalDialogHost()->GetHostView());
1225 web_contents_modal_dialog_manager
->ShowDialog(window_
->GetNativeView());
1226 focus_manager_
= window_
->GetFocusManager();
1227 focus_manager_
->AddFocusChangeListener(this);
1229 ShowDialogInMode(DETAIL_INPUT
);
1231 // Listen for size changes on the browser.
1232 views::Widget
* browser_widget
=
1233 views::Widget::GetTopLevelWidgetForNativeView(
1234 delegate_
->GetWebContents()->GetView()->GetNativeView());
1235 observer_
.Add(browser_widget
);
1238 void AutofillDialogViews::Hide() {
1243 void AutofillDialogViews::UpdatesStarted() {
1247 void AutofillDialogViews::UpdatesFinished() {
1249 DCHECK_GE(updates_scope_
, 0);
1250 if (updates_scope_
== 0 && needs_update_
) {
1251 needs_update_
= false;
1252 ContentsPreferredSizeChanged();
1256 void AutofillDialogViews::UpdateAccountChooser() {
1257 account_chooser_
->Update();
1259 bool show_loading
= delegate_
->ShouldShowSpinner();
1260 if (show_loading
!= loading_shield_
->visible()) {
1262 loading_shield_height_
= std::max(kInitialLoadingShieldHeight
,
1263 GetContentsBounds().height());
1264 ShowDialogInMode(LOADING
);
1266 bool show_sign_in
= delegate_
->ShouldShowSignInWebView();
1267 ShowDialogInMode(show_sign_in
? SIGN_IN
: DETAIL_INPUT
);
1271 ContentsPreferredSizeChanged();
1274 // Update legal documents for the account.
1275 if (footnote_view_
) {
1276 const base::string16 text
= delegate_
->LegalDocumentsText();
1277 legal_document_view_
->SetText(text
);
1279 if (!text
.empty()) {
1280 const std::vector
<gfx::Range
>& link_ranges
=
1281 delegate_
->LegalDocumentLinks();
1282 for (size_t i
= 0; i
< link_ranges
.size(); ++i
) {
1283 views::StyledLabel::RangeStyleInfo link_range_info
=
1284 views::StyledLabel::RangeStyleInfo::CreateForLink();
1285 link_range_info
.disable_line_wrapping
= false;
1286 legal_document_view_
->AddStyleRange(link_ranges
[i
], link_range_info
);
1290 footnote_view_
->SetVisible(!text
.empty());
1291 ContentsPreferredSizeChanged();
1295 GetWidget()->UpdateWindowTitle();
1298 void AutofillDialogViews::UpdateButtonStrip() {
1299 button_strip_extra_view_
->SetVisible(
1300 GetDialogButtons() != ui::DIALOG_BUTTON_NONE
);
1301 UpdateButtonStripExtraView();
1302 GetDialogClientView()->UpdateDialogButtons();
1304 ContentsPreferredSizeChanged();
1307 void AutofillDialogViews::UpdateOverlay() {
1308 overlay_view_
->UpdateState();
1309 ContentsPreferredSizeChanged();
1312 void AutofillDialogViews::UpdateDetailArea() {
1313 scrollable_area_
->SetVisible(true);
1314 ContentsPreferredSizeChanged();
1317 void AutofillDialogViews::UpdateForErrors() {
1321 void AutofillDialogViews::UpdateNotificationArea() {
1322 DCHECK(notification_area_
);
1323 notification_area_
->SetNotifications(delegate_
->CurrentNotifications());
1324 ContentsPreferredSizeChanged();
1327 void AutofillDialogViews::UpdateSection(DialogSection section
) {
1328 UpdateSectionImpl(section
, true);
1331 void AutofillDialogViews::UpdateErrorBubble() {
1332 if (!delegate_
->ShouldShowErrorBubble())
1336 void AutofillDialogViews::FillSection(DialogSection section
,
1337 ServerFieldType originating_type
) {
1338 DetailsGroup
* group
= GroupForSection(section
);
1339 // Make sure to overwrite the originating input if it exists.
1340 TextfieldMap::iterator text_mapping
=
1341 group
->textfields
.find(originating_type
);
1342 if (text_mapping
!= group
->textfields
.end())
1343 text_mapping
->second
->SetText(base::string16());
1345 // If the Autofill data comes from a credit card, make sure to overwrite the
1346 // CC comboboxes (even if they already have something in them). If the
1347 // Autofill data comes from an AutofillProfile, leave the comboboxes alone.
1348 if (section
== GetCreditCardSection() &&
1349 AutofillType(originating_type
).group() == CREDIT_CARD
) {
1350 for (ComboboxMap::const_iterator it
= group
->comboboxes
.begin();
1351 it
!= group
->comboboxes
.end(); ++it
) {
1352 if (AutofillType(it
->first
).group() == CREDIT_CARD
)
1353 it
->second
->SetSelectedIndex(it
->second
->model()->GetDefaultIndex());
1357 UpdateSectionImpl(section
, false);
1360 void AutofillDialogViews::GetUserInput(DialogSection section
,
1361 FieldValueMap
* output
) {
1362 DetailsGroup
* group
= GroupForSection(section
);
1363 for (TextfieldMap::const_iterator it
= group
->textfields
.begin();
1364 it
!= group
->textfields
.end(); ++it
) {
1365 output
->insert(std::make_pair(it
->first
, it
->second
->text()));
1367 for (ComboboxMap::const_iterator it
= group
->comboboxes
.begin();
1368 it
!= group
->comboboxes
.end(); ++it
) {
1369 output
->insert(std::make_pair(it
->first
,
1370 it
->second
->model()->GetItemAt(it
->second
->selected_index())));
1374 base::string16
AutofillDialogViews::GetCvc() {
1375 return GroupForSection(GetCreditCardSection())->suggested_info
->
1376 decorated_textfield()->text();
1379 bool AutofillDialogViews::HitTestInput(ServerFieldType type
,
1380 const gfx::Point
& screen_point
) {
1381 views::View
* view
= TextfieldForType(type
);
1383 view
= ComboboxForType(type
);
1386 gfx::Point
target_point(screen_point
);
1387 views::View::ConvertPointFromScreen(view
, &target_point
);
1388 return view
->HitTestPoint(target_point
);
1395 bool AutofillDialogViews::SaveDetailsLocally() {
1396 DCHECK(save_in_chrome_checkbox_
->visible());
1397 return save_in_chrome_checkbox_
->checked();
1400 const content::NavigationController
* AutofillDialogViews::ShowSignIn() {
1401 // The initial minimum width and height are set such that the dialog
1402 // won't change size before the page is loaded.
1403 int min_width
= GetContentsBounds().width();
1404 // The height has to include the button strip.
1405 int min_height
= GetDialogClientView()->GetContentsBounds().height();
1407 // TODO(abodenha): We should be able to use the WebContents of the WebView
1408 // to navigate instead of LoadInitialURL. Figure out why it doesn't work.
1409 sign_in_delegate_
.reset(
1410 new AutofillDialogSignInDelegate(
1412 sign_in_web_view_
->GetWebContents(),
1413 delegate_
->GetWebContents(),
1414 gfx::Size(min_width
, min_height
), GetMaximumSignInViewSize()));
1415 sign_in_web_view_
->LoadInitialURL(delegate_
->SignInUrl());
1417 ShowDialogInMode(SIGN_IN
);
1419 ContentsPreferredSizeChanged();
1421 return &sign_in_web_view_
->web_contents()->GetController();
1424 void AutofillDialogViews::HideSignIn() {
1425 sign_in_web_view_
->SetWebContents(NULL
);
1427 if (delegate_
->ShouldShowSpinner()) {
1428 UpdateAccountChooser();
1430 ShowDialogInMode(DETAIL_INPUT
);
1433 DCHECK(!sign_in_web_view_
->visible());
1435 ContentsPreferredSizeChanged();
1438 void AutofillDialogViews::ModelChanged() {
1439 menu_runner_
.reset();
1441 for (DetailGroupMap::const_iterator iter
= detail_groups_
.begin();
1442 iter
!= detail_groups_
.end(); ++iter
) {
1443 UpdateDetailsGroupState(iter
->second
);
1447 TestableAutofillDialogView
* AutofillDialogViews::GetTestableView() {
1451 void AutofillDialogViews::OnSignInResize(const gfx::Size
& pref_size
) {
1452 sign_in_web_view_
->SetPreferredSize(pref_size
);
1453 ContentsPreferredSizeChanged();
1456 void AutofillDialogViews::SubmitForTesting() {
1460 void AutofillDialogViews::CancelForTesting() {
1461 GetDialogClientView()->CancelWindow();
1464 base::string16
AutofillDialogViews::GetTextContentsOfInput(
1465 ServerFieldType type
) {
1466 views::Textfield
* textfield
= TextfieldForType(type
);
1468 return textfield
->text();
1470 views::Combobox
* combobox
= ComboboxForType(type
);
1472 return combobox
->model()->GetItemAt(combobox
->selected_index());
1475 return base::string16();
1478 void AutofillDialogViews::SetTextContentsOfInput(
1479 ServerFieldType type
,
1480 const base::string16
& contents
) {
1481 views::Textfield
* textfield
= TextfieldForType(type
);
1483 textfield
->SetText(contents
);
1487 views::Combobox
* combobox
= ComboboxForType(type
);
1489 SelectComboboxValueOrSetToDefault(combobox
, contents
);
1496 void AutofillDialogViews::SetTextContentsOfSuggestionInput(
1497 DialogSection section
,
1498 const base::string16
& text
) {
1499 GroupForSection(section
)->suggested_info
->decorated_textfield()->
1503 void AutofillDialogViews::ActivateInput(ServerFieldType type
) {
1504 InputEditedOrActivated(type
, gfx::Rect(), false);
1507 gfx::Size
AutofillDialogViews::GetSize() const {
1508 return GetWidget() ? GetWidget()->GetRootView()->size() : gfx::Size();
1511 content::WebContents
* AutofillDialogViews::GetSignInWebContents() {
1512 return sign_in_web_view_
->web_contents();
1515 bool AutofillDialogViews::IsShowingOverlay() const {
1516 return overlay_view_
->visible();
1519 gfx::Size
AutofillDialogViews::GetPreferredSize() {
1520 if (preferred_size_
.IsEmpty())
1521 preferred_size_
= CalculatePreferredSize(false);
1523 return preferred_size_
;
1526 gfx::Size
AutofillDialogViews::GetMinimumSize() {
1527 return CalculatePreferredSize(true);
1530 void AutofillDialogViews::Layout() {
1531 const gfx::Rect content_bounds
= GetContentsBounds();
1532 if (sign_in_web_view_
->visible()) {
1533 sign_in_web_view_
->SetBoundsRect(content_bounds
);
1537 if (loading_shield_
->visible()) {
1538 loading_shield_
->SetBoundsRect(bounds());
1542 const int x
= content_bounds
.x();
1543 const int y
= content_bounds
.y();
1544 const int width
= content_bounds
.width();
1545 // Layout notification area at top of dialog.
1546 int notification_height
= notification_area_
->GetHeightForWidth(width
);
1547 notification_area_
->SetBounds(x
, y
, width
, notification_height
);
1549 // The rest (the |scrollable_area_|) takes up whatever's left.
1550 if (scrollable_area_
->visible()) {
1552 if (notification_height
> notification_area_
->GetInsets().height())
1553 scroll_y
+= notification_height
+ views::kRelatedControlVerticalSpacing
;
1555 int scroll_bottom
= content_bounds
.bottom();
1556 DCHECK_EQ(scrollable_area_
->contents(), details_container_
);
1557 details_container_
->SizeToPreferredSize();
1558 details_container_
->Layout();
1559 // TODO(estade): remove this hack. See crbug.com/285996
1560 details_container_
->set_ignore_layouts(true);
1561 scrollable_area_
->SetBounds(x
, scroll_y
, width
, scroll_bottom
- scroll_y
);
1562 details_container_
->set_ignore_layouts(false);
1566 error_bubble_
->UpdatePosition();
1569 void AutofillDialogViews::OnNativeThemeChanged(
1570 const ui::NativeTheme
* theme
) {
1571 if (!legal_document_view_
)
1574 // NOTE: This color may change because of |auto_color_readability|, set on
1575 // |legal_document_view_|.
1576 views::StyledLabel::RangeStyleInfo default_style
;
1577 default_style
.color
=
1578 theme
->GetSystemColor(ui::NativeTheme::kColorId_LabelDisabledColor
);
1580 legal_document_view_
->SetDefaultStyle(default_style
);
1583 base::string16
AutofillDialogViews::GetWindowTitle() const {
1584 base::string16 title
= delegate_
->DialogTitle();
1585 // Hack alert: we don't want the dialog to jiggle when a title is added or
1586 // removed. Setting a non-empty string here keeps the dialog's title bar the
1588 return title
.empty() ? base::ASCIIToUTF16(" ") : title
;
1591 void AutofillDialogViews::WindowClosing() {
1592 focus_manager_
->RemoveFocusChangeListener(this);
1595 void AutofillDialogViews::DeleteDelegate() {
1597 // |this| belongs to the controller (|delegate_|).
1598 delegate_
->ViewClosed();
1601 int AutofillDialogViews::GetDialogButtons() const {
1602 return delegate_
->GetDialogButtons();
1605 int AutofillDialogViews::GetDefaultDialogButton() const {
1606 if (GetDialogButtons() & ui::DIALOG_BUTTON_OK
)
1607 return ui::DIALOG_BUTTON_OK
;
1609 return ui::DIALOG_BUTTON_NONE
;
1612 base::string16
AutofillDialogViews::GetDialogButtonLabel(
1613 ui::DialogButton button
) const {
1614 return button
== ui::DIALOG_BUTTON_OK
?
1615 delegate_
->ConfirmButtonText() : delegate_
->CancelButtonText();
1618 bool AutofillDialogViews::ShouldDefaultButtonBeBlue() const {
1622 bool AutofillDialogViews::IsDialogButtonEnabled(ui::DialogButton button
) const {
1623 return delegate_
->IsDialogButtonEnabled(button
);
1626 views::View
* AutofillDialogViews::GetInitiallyFocusedView() {
1627 if (!window_
|| !focus_manager_
)
1630 if (sign_in_web_view_
->visible())
1631 return sign_in_web_view_
;
1633 if (loading_shield_
->visible())
1634 return views::DialogDelegateView::GetInitiallyFocusedView();
1636 DCHECK(scrollable_area_
->visible());
1638 views::FocusManager
* manager
= focus_manager_
;
1639 for (views::View
* next
= scrollable_area_
;
1641 next
= manager
->GetNextFocusableView(next
, window_
, false, true)) {
1645 // If there are no invalid inputs, return the first input found. Otherwise,
1646 // return the first invalid input found.
1647 if (validity_map_
.empty() ||
1648 validity_map_
.find(next
) != validity_map_
.end()) {
1653 return views::DialogDelegateView::GetInitiallyFocusedView();
1656 views::View
* AutofillDialogViews::CreateExtraView() {
1657 return button_strip_extra_view_
;
1660 views::View
* AutofillDialogViews::CreateTitlebarExtraView() {
1661 return account_chooser_
;
1664 views::View
* AutofillDialogViews::CreateFootnoteView() {
1665 footnote_view_
= new LayoutPropagationView();
1666 footnote_view_
->SetLayoutManager(
1667 new views::BoxLayout(views::BoxLayout::kVertical
,
1671 footnote_view_
->set_border(
1672 views::Border::CreateSolidSidedBorder(1, 0, 0, 0, kSubtleBorderColor
));
1673 footnote_view_
->set_background(
1674 views::Background::CreateSolidBackground(kShadingColor
));
1676 legal_document_view_
= new views::StyledLabel(base::string16(), this);
1677 OnNativeThemeChanged(GetNativeTheme());
1679 footnote_view_
->AddChildView(legal_document_view_
);
1680 footnote_view_
->SetVisible(false);
1682 return footnote_view_
;
1685 views::View
* AutofillDialogViews::CreateOverlayView() {
1686 return overlay_view_
;
1689 bool AutofillDialogViews::Cancel() {
1690 return delegate_
->OnCancel();
1693 bool AutofillDialogViews::Accept() {
1695 return delegate_
->OnAccept();
1697 // |ValidateForm()| failed; there should be invalid views in |validity_map_|.
1698 DCHECK(!validity_map_
.empty());
1704 // TODO(wittman): Remove this override once we move to the new style frame view
1706 views::NonClientFrameView
* AutofillDialogViews::CreateNonClientFrameView(
1707 views::Widget
* widget
) {
1708 return CreateConstrainedStyleNonClientFrameView(
1710 delegate_
->GetWebContents()->GetBrowserContext());
1713 void AutofillDialogViews::ContentsChanged(views::Textfield
* sender
,
1714 const base::string16
& new_contents
) {
1715 InputEditedOrActivated(TypeForTextfield(sender
),
1716 sender
->GetBoundsInScreen(),
1720 bool AutofillDialogViews::HandleKeyEvent(views::Textfield
* sender
,
1721 const ui::KeyEvent
& key_event
) {
1722 ui::KeyEvent
copy(key_event
);
1723 content::NativeWebKeyboardEvent
event(©
);
1724 return delegate_
->HandleKeyPressEventInInput(event
);
1727 bool AutofillDialogViews::HandleMouseEvent(views::Textfield
* sender
,
1728 const ui::MouseEvent
& mouse_event
) {
1729 if (mouse_event
.IsLeftMouseButton() && sender
->HasFocus()) {
1730 InputEditedOrActivated(TypeForTextfield(sender
),
1731 sender
->GetBoundsInScreen(),
1733 // Show an error bubble if a user clicks on an input that's already focused
1735 ShowErrorBubbleForViewIfNecessary(sender
);
1741 void AutofillDialogViews::OnWillChangeFocus(
1742 views::View
* focused_before
,
1743 views::View
* focused_now
) {
1744 delegate_
->FocusMoved();
1748 void AutofillDialogViews::OnDidChangeFocus(
1749 views::View
* focused_before
,
1750 views::View
* focused_now
) {
1751 // If user leaves an edit-field, revalidate the group it belongs to.
1752 if (focused_before
) {
1753 DetailsGroup
* group
= GroupForView(focused_before
);
1754 if (group
&& group
->container
->visible())
1755 ValidateGroup(*group
, VALIDATE_EDIT
);
1758 // Show an error bubble when the user focuses the input.
1760 focused_now
->ScrollRectToVisible(focused_now
->GetLocalBounds());
1761 ShowErrorBubbleForViewIfNecessary(focused_now
);
1765 void AutofillDialogViews::OnSelectedIndexChanged(views::Combobox
* combobox
) {
1766 DialogSection section
= GroupForView(combobox
)->section
;
1767 InputEditedOrActivated(TypeForCombobox(combobox
), gfx::Rect(), true);
1768 // NOTE: |combobox| may have been deleted.
1769 ValidateGroup(*GroupForSection(section
), VALIDATE_EDIT
);
1770 SetEditabilityForSection(section
);
1773 void AutofillDialogViews::StyledLabelLinkClicked(const gfx::Range
& range
,
1775 delegate_
->LegalDocumentLinkClicked(range
);
1778 void AutofillDialogViews::OnMenuButtonClicked(views::View
* source
,
1779 const gfx::Point
& point
) {
1780 DCHECK_EQ(kSuggestedButtonClassName
, source
->GetClassName());
1782 DetailsGroup
* group
= NULL
;
1783 for (DetailGroupMap::iterator iter
= detail_groups_
.begin();
1784 iter
!= detail_groups_
.end(); ++iter
) {
1785 if (source
== iter
->second
.suggested_button
) {
1786 group
= &iter
->second
;
1792 if (!group
->suggested_button
->visible())
1795 menu_runner_
.reset(new views::MenuRunner(
1796 delegate_
->MenuModelForSection(group
->section
)));
1798 group
->container
->SetActive(true);
1799 views::Button::ButtonState state
= group
->suggested_button
->state();
1800 group
->suggested_button
->SetState(views::Button::STATE_PRESSED
);
1802 gfx::Rect screen_bounds
= source
->GetBoundsInScreen();
1803 screen_bounds
.Inset(source
->GetInsets());
1804 if (menu_runner_
->RunMenuAt(source
->GetWidget(),
1807 views::MenuItemView::TOPRIGHT
,
1808 ui::MENU_SOURCE_NONE
,
1809 0) == views::MenuRunner::MENU_DELETED
) {
1813 group
->container
->SetActive(false);
1814 group
->suggested_button
->SetState(state
);
1817 gfx::Size
AutofillDialogViews::CalculatePreferredSize(bool get_minimum_size
) {
1818 gfx::Insets insets
= GetInsets();
1819 gfx::Size scroll_size
= scrollable_area_
->contents()->GetPreferredSize();
1820 // The width is always set by the scroll area.
1821 const int width
= scroll_size
.width();
1823 if (sign_in_web_view_
->visible()) {
1824 const gfx::Size size
= static_cast<views::View
*>(sign_in_web_view_
)->
1826 return gfx::Size(width
+ insets
.width(), size
.height() + insets
.height());
1829 if (overlay_view_
->visible()) {
1830 const int height
= overlay_view_
->GetHeightForContentsForWidth(width
);
1832 return gfx::Size(width
+ insets
.width(), height
+ insets
.height());
1835 if (loading_shield_
->visible()) {
1836 return gfx::Size(width
+ insets
.width(),
1837 loading_shield_height_
+ insets
.height());
1841 const int notification_height
= notification_area_
->GetHeightForWidth(width
);
1842 if (notification_height
> notification_area_
->GetInsets().height())
1843 height
+= notification_height
+ views::kRelatedControlVerticalSpacing
;
1845 if (scrollable_area_
->visible())
1846 height
+= get_minimum_size
? kMinimumContentsHeight
: scroll_size
.height();
1848 return gfx::Size(width
+ insets
.width(), height
+ insets
.height());
1851 gfx::Size
AutofillDialogViews::GetMinimumSignInViewSize() const {
1852 return gfx::Size(GetDialogClientView()->size().width() - GetInsets().width(),
1853 kMinimumContentsHeight
);
1856 gfx::Size
AutofillDialogViews::GetMaximumSignInViewSize() const {
1857 web_modal::WebContentsModalDialogHost
* dialog_host
=
1858 WebContentsModalDialogManager::FromWebContents(
1859 delegate_
->GetWebContents())->delegate()->
1860 GetWebContentsModalDialogHost();
1862 // Inset the maximum dialog height to get the maximum content height.
1863 int height
= dialog_host
->GetMaximumDialogSize().height();
1864 const int non_client_height
= GetWidget()->non_client_view()->height();
1865 const int client_height
= GetWidget()->client_view()->height();
1866 // TODO(msw): Resolve the 12 pixel discrepancy; is that the bubble border?
1867 height
-= non_client_height
- client_height
- 12;
1868 height
= std::max(height
, kMinimumContentsHeight
);
1870 // The dialog's width never changes.
1871 const int width
= GetDialogClientView()->size().width() - GetInsets().width();
1872 return gfx::Size(width
, height
);
1875 DialogSection
AutofillDialogViews::GetCreditCardSection() const {
1876 if (delegate_
->SectionIsActive(SECTION_CC
))
1879 DCHECK(delegate_
->SectionIsActive(SECTION_CC_BILLING
));
1880 return SECTION_CC_BILLING
;
1883 void AutofillDialogViews::InitChildViews() {
1884 button_strip_extra_view_
= new LayoutPropagationView();
1885 button_strip_extra_view_
->SetLayoutManager(
1886 new views::BoxLayout(views::BoxLayout::kHorizontal
, 0, 0, 0));
1888 save_in_chrome_checkbox_container_
= new views::View();
1889 save_in_chrome_checkbox_container_
->SetLayoutManager(
1890 new views::BoxLayout(views::BoxLayout::kHorizontal
, 0, 0, 7));
1891 button_strip_extra_view_
->AddChildView(save_in_chrome_checkbox_container_
);
1893 save_in_chrome_checkbox_
=
1894 new views::Checkbox(delegate_
->SaveLocallyText());
1895 save_in_chrome_checkbox_
->SetChecked(delegate_
->ShouldSaveInChrome());
1896 save_in_chrome_checkbox_container_
->AddChildView(save_in_chrome_checkbox_
);
1898 save_in_chrome_checkbox_container_
->AddChildView(
1899 new TooltipIcon(delegate_
->SaveLocallyTooltip()));
1901 button_strip_image_
= new views::ImageView();
1902 button_strip_extra_view_
->AddChildView(button_strip_image_
);
1904 account_chooser_
= new AccountChooser(delegate_
);
1905 notification_area_
= new NotificationArea(delegate_
);
1906 notification_area_
->set_arrow_centering_anchor(account_chooser_
->AsWeakPtr());
1907 AddChildView(notification_area_
);
1909 scrollable_area_
= new views::ScrollView();
1910 scrollable_area_
->set_hide_horizontal_scrollbar(true);
1911 scrollable_area_
->SetContents(CreateDetailsContainer());
1912 AddChildView(scrollable_area_
);
1914 loading_shield_
= new LoadingAnimationView(delegate_
->SpinnerText());
1915 AddChildView(loading_shield_
);
1917 sign_in_web_view_
= new views::WebView(delegate_
->profile());
1918 AddChildView(sign_in_web_view_
);
1920 overlay_view_
= new OverlayView(delegate_
);
1921 overlay_view_
->SetVisible(false);
1924 views::View
* AutofillDialogViews::CreateDetailsContainer() {
1925 details_container_
= new DetailsContainerView(
1926 base::Bind(&AutofillDialogViews::DetailsContainerBoundsChanged
,
1927 base::Unretained(this)));
1929 // A box layout is used because it respects widget visibility.
1930 details_container_
->SetLayoutManager(
1931 new views::BoxLayout(views::BoxLayout::kVertical
, 0, 0, 0));
1932 for (DetailGroupMap::iterator iter
= detail_groups_
.begin();
1933 iter
!= detail_groups_
.end(); ++iter
) {
1934 CreateDetailsSection(iter
->second
.section
);
1935 details_container_
->AddChildView(iter
->second
.container
);
1938 return details_container_
;
1941 void AutofillDialogViews::CreateDetailsSection(DialogSection section
) {
1942 // Inputs container (manual inputs + combobox).
1943 views::View
* inputs_container
= CreateInputsContainer(section
);
1945 DetailsGroup
* group
= GroupForSection(section
);
1946 // Container (holds label + inputs).
1947 group
->container
= new SectionContainer(delegate_
->LabelForSection(section
),
1949 group
->suggested_button
);
1950 DCHECK(group
->suggested_button
->parent());
1951 UpdateDetailsGroupState(*group
);
1954 views::View
* AutofillDialogViews::CreateInputsContainer(DialogSection section
) {
1955 // The |info_view| holds |manual_inputs| and |suggested_info|, allowing the
1956 // dialog to toggle which is shown.
1957 views::View
* info_view
= new views::View();
1958 info_view
->SetLayoutManager(
1959 new views::BoxLayout(views::BoxLayout::kVertical
, 0, 0, 0));
1961 DetailsGroup
* group
= GroupForSection(section
);
1962 group
->manual_input
= new views::View();
1963 InitInputsView(section
);
1964 info_view
->AddChildView(group
->manual_input
);
1966 group
->suggested_info
= new SuggestionView(this);
1967 info_view
->AddChildView(group
->suggested_info
);
1969 // TODO(estade): It might be slightly more OO if this button were created
1970 // and listened to by the section container.
1971 group
->suggested_button
= new SuggestedButton(this);
1976 // TODO(estade): we should be using Chrome-style constrained window padding
1978 void AutofillDialogViews::InitInputsView(DialogSection section
) {
1979 DetailsGroup
* group
= GroupForSection(section
);
1980 EraseInvalidViewsInGroup(group
);
1982 TextfieldMap
* textfields
= &group
->textfields
;
1983 textfields
->clear();
1985 ComboboxMap
* comboboxes
= &group
->comboboxes
;
1986 comboboxes
->clear();
1988 views::View
* view
= group
->manual_input
;
1989 view
->RemoveAllChildViews(true);
1991 views::GridLayout
* layout
= new views::GridLayout(view
);
1992 view
->SetLayoutManager(layout
);
1994 int column_set_id
= 0;
1995 const DetailInputs
& inputs
= delegate_
->RequestedFieldsForSection(section
);
1996 for (DetailInputs::const_iterator it
= inputs
.begin();
1997 it
!= inputs
.end(); ++it
) {
1998 const DetailInput
& input
= *it
;
2000 ui::ComboboxModel
* input_model
=
2001 delegate_
->ComboboxModelForAutofillType(input
.type
);
2002 scoped_ptr
<views::View
> view_to_add
;
2004 views::Combobox
* combobox
= new views::Combobox(input_model
);
2005 combobox
->set_listener(this);
2006 comboboxes
->insert(std::make_pair(input
.type
, combobox
));
2007 SelectComboboxValueOrSetToDefault(combobox
, input
.initial_value
);
2008 view_to_add
.reset(combobox
);
2010 DecoratedTextfield
* field
= new DecoratedTextfield(input
.initial_value
,
2011 input
.placeholder_text
,
2013 textfields
->insert(std::make_pair(input
.type
, field
));
2014 view_to_add
.reset(field
);
2017 if (input
.length
== DetailInput::NONE
) {
2018 other_owned_views_
.push_back(view_to_add
.release());
2022 if (input
.length
== DetailInput::LONG
)
2025 views::ColumnSet
* column_set
= layout
->GetColumnSet(column_set_id
);
2027 // Create a new column set and row.
2028 column_set
= layout
->AddColumnSet(column_set_id
);
2029 if (it
!= inputs
.begin())
2030 layout
->AddPaddingRow(0, kManualInputRowPadding
);
2031 layout
->StartRow(0, column_set_id
);
2033 // Add a new column to existing row.
2034 column_set
->AddPaddingColumn(0, views::kRelatedControlHorizontalSpacing
);
2035 // Must explicitly skip the padding column since we've already started
2037 layout
->SkipColumns(1);
2040 float expand
= input
.expand_weight
;
2041 column_set
->AddColumn(views::GridLayout::FILL
,
2042 views::GridLayout::FILL
,
2043 expand
? expand
: 1.0,
2044 views::GridLayout::USE_PREF
,
2048 // This is the same as AddView(view_to_add), except that 1 is used for the
2049 // view's preferred width. Thus the width of the column completely depends
2051 layout
->AddView(view_to_add
.release(), 1, 1,
2052 views::GridLayout::FILL
, views::GridLayout::FILL
,
2055 if (input
.length
== DetailInput::LONG
||
2056 input
.length
== DetailInput::SHORT_EOL
) {
2061 SetIconsForSection(section
);
2064 void AutofillDialogViews::ShowDialogInMode(DialogMode dialog_mode
) {
2065 loading_shield_
->SetVisible(dialog_mode
== LOADING
);
2066 sign_in_web_view_
->SetVisible(dialog_mode
== SIGN_IN
);
2067 notification_area_
->SetVisible(dialog_mode
== DETAIL_INPUT
);
2068 scrollable_area_
->SetVisible(dialog_mode
== DETAIL_INPUT
);
2072 void AutofillDialogViews::UpdateSectionImpl(
2073 DialogSection section
,
2074 bool clobber_inputs
) {
2075 DetailsGroup
* group
= GroupForSection(section
);
2077 if (clobber_inputs
) {
2078 InitInputsView(section
);
2080 const DetailInputs
& updated_inputs
=
2081 delegate_
->RequestedFieldsForSection(section
);
2083 for (DetailInputs::const_iterator iter
= updated_inputs
.begin();
2084 iter
!= updated_inputs
.end(); ++iter
) {
2085 const DetailInput
& input
= *iter
;
2087 TextfieldMap::iterator text_mapping
= group
->textfields
.find(input
.type
);
2088 if (text_mapping
!= group
->textfields
.end()) {
2089 DecoratedTextfield
* decorated
= text_mapping
->second
;
2090 if (decorated
->text().empty())
2091 decorated
->SetText(input
.initial_value
);
2094 ComboboxMap::iterator combo_mapping
= group
->comboboxes
.find(input
.type
);
2095 if (combo_mapping
!= group
->comboboxes
.end()) {
2096 views::Combobox
* combobox
= combo_mapping
->second
;
2097 if (combobox
->selected_index() == combobox
->model()->GetDefaultIndex())
2098 SelectComboboxValueOrSetToDefault(combobox
, input
.initial_value
);
2102 SetIconsForSection(section
);
2105 SetEditabilityForSection(section
);
2106 UpdateDetailsGroupState(*group
);
2109 void AutofillDialogViews::UpdateDetailsGroupState(const DetailsGroup
& group
) {
2110 const SuggestionState
& suggestion_state
=
2111 delegate_
->SuggestionStateForSection(group
.section
);
2112 group
.suggested_info
->SetState(suggestion_state
);
2113 group
.manual_input
->SetVisible(!suggestion_state
.visible
);
2115 UpdateButtonStripExtraView();
2117 const bool has_menu
= !!delegate_
->MenuModelForSection(group
.section
);
2119 if (group
.suggested_button
)
2120 group
.suggested_button
->SetVisible(has_menu
);
2122 if (group
.container
) {
2123 group
.container
->SetForwardMouseEvents(
2124 has_menu
&& suggestion_state
.visible
);
2125 group
.container
->SetVisible(delegate_
->SectionIsActive(group
.section
));
2126 if (group
.container
->visible())
2127 ValidateGroup(group
, VALIDATE_EDIT
);
2130 ContentsPreferredSizeChanged();
2133 void AutofillDialogViews::FocusInitialView() {
2134 views::View
* to_focus
= GetInitiallyFocusedView();
2135 if (to_focus
&& !to_focus
->HasFocus())
2136 to_focus
->RequestFocus();
2140 void AutofillDialogViews::SetValidityForInput(
2142 const base::string16
& message
) {
2143 bool invalid
= !message
.empty();
2144 input
->SetInvalid(invalid
);
2147 validity_map_
[input
] = message
;
2149 validity_map_
.erase(input
);
2151 if (error_bubble_
&& error_bubble_
->anchor() == input
) {
2152 validity_map_
.erase(input
);
2158 void AutofillDialogViews::ShowErrorBubbleForViewIfNecessary(views::View
* view
) {
2159 if (!view
->GetWidget())
2162 if (!delegate_
->ShouldShowErrorBubble()) {
2163 DCHECK(!error_bubble_
);
2167 std::map
<views::View
*, base::string16
>::iterator error_message
=
2168 validity_map_
.find(view
);
2169 if (error_message
!= validity_map_
.end()) {
2170 view
->ScrollRectToVisible(view
->GetLocalBounds());
2172 if (!error_bubble_
|| error_bubble_
->anchor() != view
) {
2174 error_bubble_
= new InfoBubble(view
, error_message
->second
);
2175 error_bubble_
->set_align_to_anchor_edge(true);
2176 error_bubble_
->set_preferred_width(
2177 (kSectionContainerWidth
- views::kRelatedControlVerticalSpacing
) / 2);
2178 bool show_above
= view
->GetClassName() == views::Combobox::kViewClassName
;
2179 error_bubble_
->set_show_above_anchor(show_above
);
2180 error_bubble_
->Show();
2181 observer_
.Add(error_bubble_
->GetWidget());
2186 void AutofillDialogViews::HideErrorBubble() {
2188 error_bubble_
->Hide();
2191 void AutofillDialogViews::MarkInputsInvalid(
2192 DialogSection section
,
2193 const ValidityMessages
& messages
,
2194 bool overwrite_unsure
) {
2195 DetailsGroup
* group
= GroupForSection(section
);
2196 DCHECK(group
->container
->visible());
2198 if (group
->manual_input
->visible()) {
2199 for (TextfieldMap::const_iterator iter
= group
->textfields
.begin();
2200 iter
!= group
->textfields
.end(); ++iter
) {
2201 const ValidityMessage
& message
=
2202 messages
.GetMessageOrDefault(iter
->first
);
2203 if (overwrite_unsure
|| message
.sure
)
2204 SetValidityForInput(iter
->second
, message
.text
);
2206 for (ComboboxMap::const_iterator iter
= group
->comboboxes
.begin();
2207 iter
!= group
->comboboxes
.end(); ++iter
) {
2208 const ValidityMessage
& message
=
2209 messages
.GetMessageOrDefault(iter
->first
);
2210 if (overwrite_unsure
|| message
.sure
)
2211 SetValidityForInput(iter
->second
, message
.text
);
2214 EraseInvalidViewsInGroup(group
);
2216 if (section
== GetCreditCardSection()) {
2217 // Special case CVC as it's not part of |group->manual_input|.
2218 const ValidityMessage
& message
=
2219 messages
.GetMessageOrDefault(CREDIT_CARD_VERIFICATION_CODE
);
2220 if (overwrite_unsure
|| message
.sure
) {
2221 SetValidityForInput(group
->suggested_info
->decorated_textfield(),
2228 bool AutofillDialogViews::ValidateGroup(const DetailsGroup
& group
,
2229 ValidationType validation_type
) {
2230 DCHECK(group
.container
->visible());
2232 FieldValueMap detail_outputs
;
2234 if (group
.manual_input
->visible()) {
2235 for (TextfieldMap::const_iterator iter
= group
.textfields
.begin();
2236 iter
!= group
.textfields
.end(); ++iter
) {
2237 if (!iter
->second
->editable())
2240 detail_outputs
[iter
->first
] = iter
->second
->text();
2242 for (ComboboxMap::const_iterator iter
= group
.comboboxes
.begin();
2243 iter
!= group
.comboboxes
.end(); ++iter
) {
2244 if (!iter
->second
->enabled())
2247 views::Combobox
* combobox
= iter
->second
;
2248 base::string16 item
=
2249 combobox
->model()->GetItemAt(combobox
->selected_index());
2250 detail_outputs
[iter
->first
] = item
;
2252 } else if (group
.section
== GetCreditCardSection()) {
2253 DecoratedTextfield
* decorated_cvc
=
2254 group
.suggested_info
->decorated_textfield();
2255 if (decorated_cvc
->visible())
2256 detail_outputs
[CREDIT_CARD_VERIFICATION_CODE
] = decorated_cvc
->text();
2259 ValidityMessages validity
= delegate_
->InputsAreValid(group
.section
,
2261 MarkInputsInvalid(group
.section
, validity
, validation_type
== VALIDATE_FINAL
);
2263 // If there are any validation errors, sure or unsure, the group is invalid.
2264 return !validity
.HasErrors();
2267 bool AutofillDialogViews::ValidateForm() {
2268 bool all_valid
= true;
2269 validity_map_
.clear();
2271 for (DetailGroupMap::iterator iter
= detail_groups_
.begin();
2272 iter
!= detail_groups_
.end(); ++iter
) {
2273 const DetailsGroup
& group
= iter
->second
;
2274 if (!group
.container
->visible())
2277 if (!ValidateGroup(group
, VALIDATE_FINAL
))
2284 void AutofillDialogViews::InputEditedOrActivated(ServerFieldType type
,
2285 const gfx::Rect
& bounds
,
2287 DCHECK_NE(UNKNOWN_TYPE
, type
);
2289 DecoratedTextfield
* decorated
= TextfieldForType(type
);
2290 DetailsGroup
* group
= decorated
?
2291 GroupForView(decorated
) : GroupForView(ComboboxForType(type
));
2294 delegate_
->UserEditedOrActivatedInput(group
->section
,
2296 GetWidget()->GetNativeView(),
2298 GetTextContentsOfInput(type
),
2301 // If the field is a textfield and is invalid, check if the text is now valid.
2302 // Many fields (i.e. CC#) are invalid for most of the duration of editing,
2303 // so flagging them as invalid prematurely is not helpful. However,
2304 // correcting a minor mistake (i.e. a wrong CC digit) should immediately
2305 // result in validation - positive user feedback.
2306 if (decorated
&& decorated
->invalid() && was_edit
) {
2307 SetValidityForInput(
2309 delegate_
->InputValidityMessage(
2310 group
->section
, type
, decorated
->text()));
2312 // If the field transitioned from invalid to valid, re-validate the group,
2313 // since inter-field checks become meaningful with valid fields.
2314 if (!decorated
->invalid())
2315 ValidateGroup(*group
, VALIDATE_EDIT
);
2318 if (delegate_
->FieldControlsIcons(type
))
2319 SetIconsForSection(group
->section
);
2321 SetEditabilityForSection(group
->section
);
2324 void AutofillDialogViews::UpdateButtonStripExtraView() {
2325 save_in_chrome_checkbox_container_
->SetVisible(
2326 delegate_
->ShouldOfferToSaveInChrome());
2328 gfx::Image image
= delegate_
->ButtonStripImage();
2329 button_strip_image_
->SetVisible(!image
.IsEmpty());
2330 button_strip_image_
->SetImage(image
.AsImageSkia());
2333 void AutofillDialogViews::ContentsPreferredSizeChanged() {
2334 if (updates_scope_
!= 0) {
2335 needs_update_
= true;
2339 preferred_size_
= gfx::Size();
2341 if (GetWidget() && delegate_
&& delegate_
->GetWebContents()) {
2342 UpdateWebContentsModalDialogPosition(
2344 WebContentsModalDialogManager::FromWebContents(
2345 delegate_
->GetWebContents())->delegate()->
2346 GetWebContentsModalDialogHost());
2347 SetBoundsRect(bounds());
2351 AutofillDialogViews::DetailsGroup
* AutofillDialogViews::GroupForSection(
2352 DialogSection section
) {
2353 return &detail_groups_
.find(section
)->second
;
2356 AutofillDialogViews::DetailsGroup
* AutofillDialogViews::GroupForView(
2357 views::View
* view
) {
2360 for (DetailGroupMap::iterator iter
= detail_groups_
.begin();
2361 iter
!= detail_groups_
.end(); ++iter
) {
2362 DetailsGroup
* group
= &iter
->second
;
2363 if (view
->parent() == group
->manual_input
)
2366 views::View
* decorated
=
2367 view
->GetAncestorWithClassName(DecoratedTextfield::kViewClassName
);
2369 // Textfields need to check a second case, since they can be suggested
2370 // inputs instead of directly editable inputs. Those are accessed via
2371 // |suggested_info|.
2373 decorated
== group
->suggested_info
->decorated_textfield()) {
2381 void AutofillDialogViews::EraseInvalidViewsInGroup(const DetailsGroup
* group
) {
2382 std::map
<views::View
*, base::string16
>::iterator it
= validity_map_
.begin();
2383 while (it
!= validity_map_
.end()) {
2384 if (GroupForView(it
->first
) == group
)
2385 validity_map_
.erase(it
++);
2391 DecoratedTextfield
* AutofillDialogViews::TextfieldForType(
2392 ServerFieldType type
) {
2393 if (type
== CREDIT_CARD_VERIFICATION_CODE
) {
2394 DetailsGroup
* group
= GroupForSection(GetCreditCardSection());
2395 if (!group
->manual_input
->visible())
2396 return group
->suggested_info
->decorated_textfield();
2399 for (DetailGroupMap::iterator iter
= detail_groups_
.begin();
2400 iter
!= detail_groups_
.end(); ++iter
) {
2401 const DetailsGroup
& group
= iter
->second
;
2402 if (!delegate_
->SectionIsActive(group
.section
))
2405 TextfieldMap::const_iterator text_mapping
= group
.textfields
.find(type
);
2406 if (text_mapping
!= group
.textfields
.end())
2407 return text_mapping
->second
;
2413 ServerFieldType
AutofillDialogViews::TypeForTextfield(
2414 const views::Textfield
* textfield
) {
2415 DetailsGroup
* cc_group
= GroupForSection(GetCreditCardSection());
2416 if (textfield
== cc_group
->suggested_info
->decorated_textfield())
2417 return CREDIT_CARD_VERIFICATION_CODE
;
2419 for (DetailGroupMap::const_iterator it
= detail_groups_
.begin();
2420 it
!= detail_groups_
.end(); ++it
) {
2421 if (!delegate_
->SectionIsActive(it
->second
.section
))
2424 for (TextfieldMap::const_iterator text_it
= it
->second
.textfields
.begin();
2425 text_it
!= it
->second
.textfields
.end(); ++text_it
) {
2426 if (textfield
== text_it
->second
)
2427 return text_it
->first
;
2431 return UNKNOWN_TYPE
;
2434 views::Combobox
* AutofillDialogViews::ComboboxForType(
2435 ServerFieldType type
) {
2436 for (DetailGroupMap::iterator iter
= detail_groups_
.begin();
2437 iter
!= detail_groups_
.end(); ++iter
) {
2438 const DetailsGroup
& group
= iter
->second
;
2439 if (!delegate_
->SectionIsActive(group
.section
))
2442 ComboboxMap::const_iterator combo_mapping
= group
.comboboxes
.find(type
);
2443 if (combo_mapping
!= group
.comboboxes
.end())
2444 return combo_mapping
->second
;
2450 ServerFieldType
AutofillDialogViews::TypeForCombobox(
2451 const views::Combobox
* combobox
) const {
2452 for (DetailGroupMap::const_iterator it
= detail_groups_
.begin();
2453 it
!= detail_groups_
.end(); ++it
) {
2454 const DetailsGroup
& group
= it
->second
;
2455 if (!delegate_
->SectionIsActive(group
.section
))
2458 for (ComboboxMap::const_iterator combo_it
= group
.comboboxes
.begin();
2459 combo_it
!= group
.comboboxes
.end(); ++combo_it
) {
2460 if (combo_it
->second
== combobox
)
2461 return combo_it
->first
;
2465 return UNKNOWN_TYPE
;
2468 void AutofillDialogViews::DetailsContainerBoundsChanged() {
2470 error_bubble_
->UpdatePosition();
2473 void AutofillDialogViews::SetIconsForSection(DialogSection section
) {
2474 FieldValueMap user_input
;
2475 GetUserInput(section
, &user_input
);
2476 FieldIconMap field_icons
= delegate_
->IconsForFields(user_input
);
2477 TextfieldMap
* textfields
= &GroupForSection(section
)->textfields
;
2478 for (TextfieldMap::const_iterator textfield_it
= textfields
->begin();
2479 textfield_it
!= textfields
->end();
2481 ServerFieldType field_type
= textfield_it
->first
;
2482 FieldIconMap::const_iterator field_icon_it
= field_icons
.find(field_type
);
2483 DecoratedTextfield
* textfield
= textfield_it
->second
;
2484 if (field_icon_it
!= field_icons
.end())
2485 textfield
->SetIcon(field_icon_it
->second
);
2487 textfield
->SetTooltipIcon(delegate_
->TooltipForField(field_type
));
2491 void AutofillDialogViews::SetEditabilityForSection(DialogSection section
) {
2492 const DetailInputs
& inputs
=
2493 delegate_
->RequestedFieldsForSection(section
);
2494 DetailsGroup
* group
= GroupForSection(section
);
2496 for (DetailInputs::const_iterator iter
= inputs
.begin();
2497 iter
!= inputs
.end(); ++iter
) {
2498 const DetailInput
& input
= *iter
;
2499 bool editable
= delegate_
->InputIsEditable(input
, section
);
2501 TextfieldMap::iterator text_mapping
= group
->textfields
.find(input
.type
);
2502 if (text_mapping
!= group
->textfields
.end()) {
2503 DecoratedTextfield
* decorated
= text_mapping
->second
;
2504 decorated
->SetEditable(editable
);
2508 ComboboxMap::iterator combo_mapping
= group
->comboboxes
.find(input
.type
);
2509 if (combo_mapping
!= group
->comboboxes
.end()) {
2510 views::Combobox
* combobox
= combo_mapping
->second
;
2511 combobox
->SetEnabled(editable
);
2516 AutofillDialogViews::DetailsGroup::DetailsGroup(DialogSection section
)
2520 suggested_info(NULL
),
2521 suggested_button(NULL
) {}
2523 AutofillDialogViews::DetailsGroup::~DetailsGroup() {}
2525 } // namespace autofill