1 // Copyright (c) 2012 The Chromium Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
5 #include "chrome/browser/ui/views/autofill/autofill_dialog_views.h"
10 #include "base/location.h"
11 #include "base/strings/utf_string_conversions.h"
12 #include "chrome/browser/profiles/profile.h"
13 #include "chrome/browser/ui/autofill/autofill_dialog_sign_in_delegate.h"
14 #include "chrome/browser/ui/autofill/autofill_dialog_view_delegate.h"
15 #include "chrome/browser/ui/autofill/loading_animation.h"
16 #include "chrome/browser/ui/views/autofill/expanding_textfield.h"
17 #include "chrome/browser/ui/views/autofill/info_bubble.h"
18 #include "chrome/browser/ui/views/autofill/tooltip_icon.h"
19 #include "chrome/browser/ui/views/constrained_window_views.h"
20 #include "components/autofill/content/browser/wallet/wallet_service_url.h"
21 #include "components/autofill/core/browser/autofill_type.h"
22 #include "components/web_modal/web_contents_modal_dialog_host.h"
23 #include "components/web_modal/web_contents_modal_dialog_manager.h"
24 #include "components/web_modal/web_contents_modal_dialog_manager_delegate.h"
25 #include "content/public/browser/native_web_keyboard_event.h"
26 #include "content/public/browser/navigation_controller.h"
27 #include "content/public/browser/web_contents.h"
28 #include "grit/theme_resources.h"
29 #include "grit/ui_resources.h"
30 #include "third_party/skia/include/core/SkColor.h"
31 #include "ui/base/models/combobox_model.h"
32 #include "ui/base/models/menu_model.h"
33 #include "ui/base/resource/resource_bundle.h"
34 #include "ui/events/event_handler.h"
35 #include "ui/gfx/animation/animation_delegate.h"
36 #include "ui/gfx/canvas.h"
37 #include "ui/gfx/color_utils.h"
38 #include "ui/gfx/font_list.h"
39 #include "ui/gfx/path.h"
40 #include "ui/gfx/point.h"
41 #include "ui/gfx/skia_util.h"
42 #include "ui/views/background.h"
43 #include "ui/views/border.h"
44 #include "ui/views/bubble/bubble_border.h"
45 #include "ui/views/bubble/bubble_frame_view.h"
46 #include "ui/views/controls/button/blue_button.h"
47 #include "ui/views/controls/button/checkbox.h"
48 #include "ui/views/controls/button/label_button.h"
49 #include "ui/views/controls/button/label_button_border.h"
50 #include "ui/views/controls/button/menu_button.h"
51 #include "ui/views/controls/combobox/combobox.h"
52 #include "ui/views/controls/image_view.h"
53 #include "ui/views/controls/label.h"
54 #include "ui/views/controls/link.h"
55 #include "ui/views/controls/menu/menu_runner.h"
56 #include "ui/views/controls/separator.h"
57 #include "ui/views/controls/styled_label.h"
58 #include "ui/views/controls/textfield/textfield.h"
59 #include "ui/views/controls/webview/webview.h"
60 #include "ui/views/layout/box_layout.h"
61 #include "ui/views/layout/fill_layout.h"
62 #include "ui/views/layout/grid_layout.h"
63 #include "ui/views/layout/layout_constants.h"
64 #include "ui/views/painter.h"
65 #include "ui/views/view_targeter.h"
66 #include "ui/views/widget/widget.h"
67 #include "ui/views/window/dialog_client_view.h"
68 #include "ui/views/window/non_client_view.h"
74 // The width for the section container.
75 const int kSectionContainerWidth = 440;
77 // The minimum useful height of the contents area of the dialog.
78 const int kMinimumContentsHeight = 101;
80 // The default height of the loading shield, also its minimum size.
81 const int kInitialLoadingShieldHeight = 150;
83 // Horizontal padding between text and other elements (in pixels).
84 const int kAroundTextPadding = 4;
86 // The space between the edges of a notification bar and the text within (in
88 const int kNotificationPadding = 17;
90 // Vertical padding above and below each detail section (in pixels).
91 const int kDetailSectionVerticalPadding = 10;
93 const int kArrowHeight = 7;
94 const int kArrowWidth = 2 * kArrowHeight;
96 // The padding inside the edges of the dialog, in pixels.
97 const int kDialogEdgePadding = 20;
99 // The vertical padding between rows of manual inputs (in pixels).
100 const int kManualInputRowPadding = 10;
102 // Slight shading for mouse hover and legal document background.
103 SkColor kShadingColor = SkColorSetARGB(7, 0, 0, 0);
105 // A border color for the legal document view.
106 SkColor kSubtleBorderColor = SkColorSetARGB(10, 0, 0, 0);
108 // The top and bottom padding, in pixels, for the suggestions menu dropdown
110 const int kMenuButtonTopInset = 3;
111 const int kMenuButtonBottomInset = 6;
113 // The height in pixels of the padding above and below the overlay message view.
114 const int kOverlayMessageVerticalPadding = 34;
116 // Spacing below image and above text messages in overlay view.
117 const int kOverlayImageBottomMargin = 100;
119 const char kNotificationAreaClassName[] = "autofill/NotificationArea";
120 const char kOverlayViewClassName[] = "autofill/OverlayView";
121 const char kSectionContainerClassName[] = "autofill/SectionContainer";
122 const char kSuggestedButtonClassName[] = "autofill/SuggestedButton";
124 // Draws an arrow at the top of |canvas| pointing to |tip_x|.
125 void DrawArrow(gfx::Canvas* canvas,
127 const SkColor& fill_color,
128 const SkColor& stroke_color) {
129 const int arrow_half_width = kArrowWidth / 2.0f;
132 arrow.moveTo(tip_x - arrow_half_width, kArrowHeight);
133 arrow.lineTo(tip_x, 0);
134 arrow.lineTo(tip_x + arrow_half_width, kArrowHeight);
137 fill_paint.setColor(fill_color);
138 canvas->DrawPath(arrow, fill_paint);
140 if (stroke_color != SK_ColorTRANSPARENT) {
141 SkPaint stroke_paint;
142 stroke_paint.setColor(stroke_color);
143 stroke_paint.setStyle(SkPaint::kStroke_Style);
144 canvas->DrawPath(arrow, stroke_paint);
148 void SelectComboboxValueOrSetToDefault(views::Combobox* combobox,
149 const base::string16& value) {
150 if (!combobox->SelectValue(value))
151 combobox->SetSelectedIndex(combobox->model()->GetDefaultIndex());
154 // This class handles layout for the first row of a SuggestionView.
155 // It exists to circumvent shortcomings of GridLayout and BoxLayout (namely that
156 // the former doesn't fully respect child visibility, and that the latter won't
157 // expand a single child).
158 class SectionRowView : public views::View {
160 SectionRowView() { SetBorder(views::Border::CreateEmptyBorder(10, 0, 0, 0)); }
162 virtual ~SectionRowView() {}
164 // views::View implementation:
165 virtual gfx::Size GetPreferredSize() const OVERRIDE {
168 for (int i = 0; i < child_count(); ++i) {
169 if (child_at(i)->visible()) {
171 width += kAroundTextPadding;
173 gfx::Size size = child_at(i)->GetPreferredSize();
174 height = std::max(height, size.height());
175 width += size.width();
179 gfx::Insets insets = GetInsets();
180 return gfx::Size(width + insets.width(), height + insets.height());
183 virtual void Layout() OVERRIDE {
184 const gfx::Rect bounds = GetContentsBounds();
186 // Icon is left aligned.
187 int start_x = bounds.x();
188 views::View* icon = child_at(0);
189 if (icon->visible()) {
190 icon->SizeToPreferredSize();
192 icon->SetY(bounds.y() +
193 (bounds.height() - icon->bounds().height()) / 2);
194 start_x += icon->bounds().width() + kAroundTextPadding;
197 // Textfield is right aligned.
198 int end_x = bounds.width();
199 views::View* textfield = child_at(2);
200 if (textfield->visible()) {
201 const int preferred_width = textfield->GetPreferredSize().width();
202 textfield->SetBounds(bounds.width() - preferred_width, bounds.y(),
203 preferred_width, bounds.height());
204 end_x = textfield->bounds().x() - kAroundTextPadding;
207 // Label takes up all the space in between.
208 views::View* label = child_at(1);
209 if (label->visible())
210 label->SetBounds(start_x, bounds.y(), end_x - start_x, bounds.height());
212 views::View::Layout();
216 DISALLOW_COPY_AND_ASSIGN(SectionRowView);
219 // A view that propagates visibility and preferred size changes.
220 class LayoutPropagationView : public views::View {
222 LayoutPropagationView() {}
223 virtual ~LayoutPropagationView() {}
226 virtual void ChildVisibilityChanged(views::View* child) OVERRIDE {
227 PreferredSizeChanged();
229 virtual void ChildPreferredSizeChanged(views::View* child) OVERRIDE {
230 PreferredSizeChanged();
234 DISALLOW_COPY_AND_ASSIGN(LayoutPropagationView);
237 // A View for a single notification banner.
238 class NotificationView : public views::View,
239 public views::ButtonListener,
240 public views::StyledLabelListener {
242 NotificationView(const DialogNotification& data,
243 AutofillDialogViewDelegate* delegate)
247 scoped_ptr<views::View> label_view;
248 if (data.HasCheckbox()) {
249 scoped_ptr<views::Checkbox> checkbox(
250 new views::Checkbox(base::string16()));
251 checkbox->SetText(data.display_text());
252 checkbox->SetTextMultiLine(true);
253 checkbox->SetHorizontalAlignment(gfx::ALIGN_LEFT);
254 checkbox->SetTextColor(views::Button::STATE_NORMAL,
255 data.GetTextColor());
256 checkbox->SetTextColor(views::Button::STATE_HOVERED,
257 data.GetTextColor());
258 checkbox->SetChecked(data.checked());
259 checkbox->set_listener(this);
260 checkbox_ = checkbox.get();
261 label_view.reset(checkbox.release());
263 scoped_ptr<views::StyledLabel> label(new views::StyledLabel(
264 data.display_text(), this));
265 label->set_auto_color_readability_enabled(false);
267 views::StyledLabel::RangeStyleInfo text_style;
268 text_style.color = data.GetTextColor();
270 if (data.link_range().is_empty()) {
271 label->AddStyleRange(gfx::Range(0, data.display_text().size()),
274 gfx::Range prefix_range(0, data.link_range().start());
275 if (!prefix_range.is_empty())
276 label->AddStyleRange(prefix_range, text_style);
278 label->AddStyleRange(
280 views::StyledLabel::RangeStyleInfo::CreateForLink());
282 gfx::Range suffix_range(data.link_range().end(),
283 data.display_text().size());
284 if (!suffix_range.is_empty())
285 label->AddStyleRange(suffix_range, text_style);
288 label_view.reset(label.release());
291 AddChildView(label_view.release());
293 if (!data.tooltip_text().empty())
294 AddChildView(new TooltipIcon(data.tooltip_text()));
297 views::Background::CreateSolidBackground(data.GetBackgroundColor()));
298 SetBorder(views::Border::CreateSolidSidedBorder(
299 1, 0, 1, 0, data.GetBorderColor()));
302 virtual ~NotificationView() {}
304 views::Checkbox* checkbox() {
308 // views::View implementation.
309 virtual gfx::Insets GetInsets() const OVERRIDE {
310 int vertical_padding = kNotificationPadding;
312 vertical_padding -= 3;
313 return gfx::Insets(vertical_padding, kDialogEdgePadding,
314 vertical_padding, kDialogEdgePadding);
317 virtual int GetHeightForWidth(int width) const OVERRIDE {
318 int label_width = width - GetInsets().width();
319 if (child_count() > 1) {
320 const views::View* tooltip_icon = child_at(1);
321 label_width -= tooltip_icon->GetPreferredSize().width() +
325 return child_at(0)->GetHeightForWidth(label_width) + GetInsets().height();
328 virtual void Layout() OVERRIDE {
329 // Surprisingly, GetContentsBounds() doesn't consult GetInsets().
330 gfx::Rect bounds = GetLocalBounds();
331 bounds.Inset(GetInsets());
332 int right_bound = bounds.right();
334 if (child_count() > 1) {
335 // The icon takes up the entire vertical space and an extra 20px on
336 // each side. This increases the hover target for the tooltip.
337 views::View* tooltip_icon = child_at(1);
338 gfx::Size icon_size = tooltip_icon->GetPreferredSize();
339 int icon_width = icon_size.width() + kDialogEdgePadding;
340 right_bound -= icon_width;
341 tooltip_icon->SetBounds(
343 icon_width + kDialogEdgePadding, GetLocalBounds().height());
346 child_at(0)->SetBounds(bounds.x(), bounds.y(),
347 right_bound - bounds.x(), bounds.height());
350 // views::ButtonListener implementation.
351 virtual void ButtonPressed(views::Button* sender,
352 const ui::Event& event) OVERRIDE {
353 DCHECK_EQ(sender, checkbox_);
354 delegate_->NotificationCheckboxStateChanged(data_.type(),
355 checkbox_->checked());
358 // views::StyledLabelListener implementation.
359 virtual void StyledLabelLinkClicked(const gfx::Range& range, int event_flags)
361 delegate_->LinkClicked(data_.link_url());
365 // The model data for this notification.
366 DialogNotification data_;
368 // The delegate that handles interaction with |this|.
369 AutofillDialogViewDelegate* delegate_;
371 // The checkbox associated with this notification, or NULL if there is none.
372 views::Checkbox* checkbox_;
374 DISALLOW_COPY_AND_ASSIGN(NotificationView);
377 // A view that displays a loading message with some dancing dots.
378 class LoadingAnimationView : public views::View,
379 public gfx::AnimationDelegate {
381 explicit LoadingAnimationView(const base::string16& text) :
382 container_(new views::View()) {
383 AddChildView(container_);
384 container_->SetLayoutManager(
385 new views::BoxLayout(views::BoxLayout::kHorizontal, 0, 0, 0));
387 const gfx::FontList& font_list =
388 ui::ResourceBundle::GetSharedInstance().GetFontList(
389 ui::ResourceBundle::LargeFont);
390 animation_.reset(new LoadingAnimation(this, font_list.GetHeight()));
392 container_->AddChildView(new views::Label(text, font_list));
394 for (size_t i = 0; i < 3; ++i) {
395 container_->AddChildView(
396 new views::Label(base::ASCIIToUTF16("."), font_list));
400 virtual ~LoadingAnimationView() {}
402 // views::View implementation.
403 virtual void SetVisible(bool visible) OVERRIDE {
409 views::View::SetVisible(visible);
412 virtual void Layout() OVERRIDE {
413 gfx::Size container_size = container_->GetPreferredSize();
414 gfx::Rect container_bounds((width() - container_size.width()) / 2,
415 (height() - container_size.height()) / 2,
416 container_size.width(),
417 container_size.height());
418 container_->SetBoundsRect(container_bounds);
419 container_->Layout();
421 for (size_t i = 0; i < 3; ++i) {
422 views::View* dot = container_->child_at(i + 1);
423 dot->SetY(dot->y() + animation_->GetCurrentValueForDot(i));
427 virtual void OnNativeThemeChanged(const ui::NativeTheme* theme) OVERRIDE {
428 set_background(views::Background::CreateSolidBackground(
429 theme->GetSystemColor(ui::NativeTheme::kColorId_DialogBackground)));
432 // gfx::AnimationDelegate implementation.
433 virtual void AnimationProgressed(const gfx::Animation* animation) OVERRIDE {
434 DCHECK_EQ(animation, animation_.get());
439 // Contains the "Loading" label and the dots.
440 views::View* container_;
442 scoped_ptr<LoadingAnimation> animation_;
444 DISALLOW_COPY_AND_ASSIGN(LoadingAnimationView);
447 // Gets either the Combobox or ExpandingTextfield that is an ancestor (including
449 views::View* GetAncestralInputView(views::View* view) {
450 if (view->GetClassName() == views::Combobox::kViewClassName)
453 return view->GetAncestorWithClassName(ExpandingTextfield::kViewClassName);
456 // A class that informs |delegate_| when an unhandled mouse press occurs.
457 class MousePressedHandler : public ui::EventHandler {
459 explicit MousePressedHandler(AutofillDialogViewDelegate* delegate)
460 : delegate_(delegate) {}
462 // ui::EventHandler implementation.
463 virtual void OnMouseEvent(ui::MouseEvent* event) OVERRIDE {
464 if (event->type() == ui::ET_MOUSE_PRESSED && !event->handled())
465 delegate_->FocusMoved();
469 AutofillDialogViewDelegate* const delegate_;
471 DISALLOW_COPY_AND_ASSIGN(MousePressedHandler);
476 // AutofillDialogViews::AccountChooser -----------------------------------------
478 AutofillDialogViews::AccountChooser::AccountChooser(
479 AutofillDialogViewDelegate* delegate)
480 : image_(new views::ImageView()),
481 menu_button_(new views::MenuButton(NULL, base::string16(), this, true)),
482 link_(new views::Link()),
483 delegate_(delegate) {
484 SetBorder(views::Border::CreateEmptyBorder(0, 0, 0, 10));
486 new views::BoxLayout(views::BoxLayout::kHorizontal, 0, 0,
487 kAroundTextPadding));
488 AddChildView(image_);
490 menu_button_->set_background(NULL);
491 menu_button_->SetBorder(views::Border::NullBorder());
492 gfx::Insets insets = GetInsets();
493 menu_button_->SetFocusPainter(
494 views::Painter::CreateDashedFocusPainterWithInsets(insets));
495 menu_button_->SetFocusable(true);
496 AddChildView(menu_button_);
498 link_->set_listener(this);
502 AutofillDialogViews::AccountChooser::~AccountChooser() {}
504 void AutofillDialogViews::AccountChooser::Update() {
505 SetVisible(delegate_->ShouldShowAccountChooser());
507 gfx::Image icon = delegate_->AccountChooserImage();
508 image_->SetImage(icon.AsImageSkia());
509 menu_button_->SetText(delegate_->AccountChooserText());
510 menu_button_->SetMinSize(gfx::Size());
512 bool show_link = !delegate_->MenuModelForAccountChooser();
513 menu_button_->SetVisible(!show_link);
514 link_->SetText(delegate_->SignInLinkText());
515 link_->SetVisible(show_link);
517 menu_runner_.reset();
519 PreferredSizeChanged();
522 void AutofillDialogViews::AccountChooser::OnMenuButtonClicked(
524 const gfx::Point& point) {
525 DCHECK_EQ(menu_button_, source);
527 ui::MenuModel* model = delegate_->MenuModelForAccountChooser();
531 menu_runner_.reset(new views::MenuRunner(model, 0));
532 if (menu_runner_->RunMenuAt(source->GetWidget(),
534 source->GetBoundsInScreen(),
535 views::MENU_ANCHOR_TOPRIGHT,
536 ui::MENU_SOURCE_NONE) ==
537 views::MenuRunner::MENU_DELETED) {
542 views::View* AutofillDialogViews::GetLoadingShieldForTesting() {
543 return loading_shield_;
546 views::WebView* AutofillDialogViews::GetSignInWebViewForTesting() {
547 return sign_in_web_view_;
550 views::View* AutofillDialogViews::GetNotificationAreaForTesting() {
551 return notification_area_;
554 views::View* AutofillDialogViews::GetScrollableAreaForTesting() {
555 return scrollable_area_;
558 void AutofillDialogViews::AccountChooser::LinkClicked(views::Link* source,
560 delegate_->SignInLinkClicked();
563 // AutofillDialogViews::OverlayView --------------------------------------------
565 AutofillDialogViews::OverlayView::OverlayView(
566 AutofillDialogViewDelegate* delegate)
567 : delegate_(delegate),
568 image_view_(new views::ImageView()),
569 message_view_(new views::Label()) {
570 message_view_->SetAutoColorReadabilityEnabled(false);
571 message_view_->SetMultiLine(true);
573 AddChildView(image_view_);
574 AddChildView(message_view_);
577 AutofillDialogViews::OverlayView::~OverlayView() {}
579 int AutofillDialogViews::OverlayView::GetHeightForContentsForWidth(int width) {
580 // In this case, 0 means "no preference".
581 if (!message_view_->visible())
584 return kOverlayImageBottomMargin +
585 views::kButtonVEdgeMarginNew +
586 message_view_->GetHeightForWidth(width) +
587 image_view_->GetHeightForWidth(width);
590 void AutofillDialogViews::OverlayView::UpdateState() {
591 const DialogOverlayState& state = delegate_->GetDialogOverlay();
593 if (state.image.IsEmpty()) {
598 image_view_->SetImage(state.image.ToImageSkia());
600 message_view_->SetVisible(!state.string.text.empty());
601 message_view_->SetText(state.string.text);
602 message_view_->SetFontList(state.string.font_list);
603 message_view_->SetEnabledColor(GetNativeTheme()->GetSystemColor(
604 ui::NativeTheme::kColorId_TextfieldReadOnlyColor));
606 message_view_->SetBorder(
607 views::Border::CreateEmptyBorder(kOverlayMessageVerticalPadding,
609 kOverlayMessageVerticalPadding,
610 kDialogEdgePadding));
615 gfx::Insets AutofillDialogViews::OverlayView::GetInsets() const {
616 return gfx::Insets(12, 12, 12, 12);
619 void AutofillDialogViews::OverlayView::Layout() {
620 gfx::Rect bounds = ContentBoundsSansBubbleBorder();
621 if (!message_view_->visible()) {
622 image_view_->SetBoundsRect(bounds);
626 int message_height = message_view_->GetHeightForWidth(bounds.width());
627 int y = bounds.bottom() - message_height;
628 message_view_->SetBounds(bounds.x(), y, bounds.width(), message_height);
630 gfx::Size image_size = image_view_->GetPreferredSize();
631 y -= image_size.height() + kOverlayImageBottomMargin;
632 image_view_->SetBounds(bounds.x(), y, bounds.width(), image_size.height());
635 const char* AutofillDialogViews::OverlayView::GetClassName() const {
636 return kOverlayViewClassName;
639 void AutofillDialogViews::OverlayView::OnPaint(gfx::Canvas* canvas) {
640 // BubbleFrameView doesn't mask the window, it just draws the border via
641 // image assets. Match that rounding here.
642 gfx::Rect rect = ContentBoundsSansBubbleBorder();
643 const SkScalar kCornerRadius = SkIntToScalar(
644 GetBubbleBorder() ? GetBubbleBorder()->GetBorderCornerRadius() : 2);
645 gfx::Path window_mask;
646 window_mask.addRoundRect(gfx::RectToSkRect(rect),
647 kCornerRadius, kCornerRadius);
648 canvas->ClipPath(window_mask, false);
650 OnPaintBackground(canvas);
652 // Draw the arrow, border, and fill for the bottom area.
653 if (message_view_->visible()) {
654 const int arrow_half_width = kArrowWidth / 2.0f;
656 int y = message_view_->y() - 1;
657 // Note that we purposely draw slightly outside of |rect| so that the
658 // stroke is hidden on the sides.
659 arrow.moveTo(rect.x() - 1, y);
660 arrow.rLineTo(rect.width() / 2 - arrow_half_width, 0);
661 arrow.rLineTo(arrow_half_width, -kArrowHeight);
662 arrow.rLineTo(arrow_half_width, kArrowHeight);
663 arrow.lineTo(rect.right() + 1, y);
664 arrow.lineTo(rect.right() + 1, rect.bottom() + 1);
665 arrow.lineTo(rect.x() - 1, rect.bottom() + 1);
668 // The mocked alpha blends were 7 for background & 10 for the border against
669 // a very bright background. The eye perceives luminance differences of
670 // darker colors much less than lighter colors, so increase the alpha blend
671 // amount the darker the color (lower the luminance).
673 SkColor background_color = background()->get_color();
674 int background_luminance =
675 color_utils::GetLuminanceForColor(background_color);
676 int background_alpha = static_cast<int>(
677 7 + 15 * (255 - background_luminance) / 255);
678 int subtle_border_alpha = static_cast<int>(
679 10 + 20 * (255 - background_luminance) / 255);
681 paint.setColor(color_utils::BlendTowardOppositeLuminance(
682 background_color, background_alpha));
683 paint.setStyle(SkPaint::kFill_Style);
684 canvas->DrawPath(arrow, paint);
685 paint.setColor(color_utils::BlendTowardOppositeLuminance(
686 background_color, subtle_border_alpha));
687 paint.setStyle(SkPaint::kStroke_Style);
688 canvas->DrawPath(arrow, paint);
691 PaintChildren(canvas, views::CullSet());
694 void AutofillDialogViews::OverlayView::OnNativeThemeChanged(
695 const ui::NativeTheme* theme) {
696 set_background(views::Background::CreateSolidBackground(
697 theme->GetSystemColor(ui::NativeTheme::kColorId_DialogBackground)));
700 views::BubbleBorder* AutofillDialogViews::OverlayView::GetBubbleBorder() {
701 views::View* frame = GetWidget()->non_client_view()->frame_view();
702 std::string bubble_frame_view_name(views::BubbleFrameView::kViewClassName);
703 if (frame->GetClassName() == bubble_frame_view_name)
704 return static_cast<views::BubbleFrameView*>(frame)->bubble_border();
709 gfx::Rect AutofillDialogViews::OverlayView::ContentBoundsSansBubbleBorder() {
710 gfx::Rect bounds = GetContentsBounds();
711 int bubble_width = 5;
712 if (GetBubbleBorder())
713 bubble_width = GetBubbleBorder()->GetBorderThickness();
714 bounds.Inset(bubble_width, bubble_width, bubble_width, bubble_width);
718 // AutofillDialogViews::NotificationArea ---------------------------------------
720 AutofillDialogViews::NotificationArea::NotificationArea(
721 AutofillDialogViewDelegate* delegate)
722 : delegate_(delegate) {
723 // Reserve vertical space for the arrow (regardless of whether one exists).
724 // The -1 accounts for the border.
725 SetBorder(views::Border::CreateEmptyBorder(kArrowHeight - 1, 0, 0, 0));
727 views::BoxLayout* box_layout =
728 new views::BoxLayout(views::BoxLayout::kVertical, 0, 0, 0);
729 SetLayoutManager(box_layout);
732 AutofillDialogViews::NotificationArea::~NotificationArea() {}
734 void AutofillDialogViews::NotificationArea::SetNotifications(
735 const std::vector<DialogNotification>& notifications) {
736 notifications_ = notifications;
738 RemoveAllChildViews(true);
740 if (notifications_.empty())
743 for (size_t i = 0; i < notifications_.size(); ++i) {
744 const DialogNotification& notification = notifications_[i];
745 scoped_ptr<NotificationView> view(new NotificationView(notification,
748 AddChildView(view.release());
751 PreferredSizeChanged();
754 gfx::Size AutofillDialogViews::NotificationArea::GetPreferredSize() const {
755 gfx::Size size = views::View::GetPreferredSize();
756 // Ensure that long notifications wrap and don't enlarge the dialog.
761 const char* AutofillDialogViews::NotificationArea::GetClassName() const {
762 return kNotificationAreaClassName;
765 void AutofillDialogViews::NotificationArea::PaintChildren(
767 const views::CullSet& cull_set) {
770 void AutofillDialogViews::NotificationArea::OnPaint(gfx::Canvas* canvas) {
771 views::View::OnPaint(canvas);
772 views::View::PaintChildren(canvas, views::CullSet());
777 GetMirroredXInView(width() - arrow_centering_anchor_->width() / 2.0f),
778 notifications_[0].GetBackgroundColor(),
779 notifications_[0].GetBorderColor());
783 void AutofillDialogViews::OnWidgetDestroying(views::Widget* widget) {
784 if (widget == window_)
785 window_->GetRootView()->RemovePostTargetHandler(event_handler_.get());
788 void AutofillDialogViews::OnWidgetClosing(views::Widget* widget) {
789 observer_.Remove(widget);
790 if (error_bubble_ && error_bubble_->GetWidget() == widget)
791 error_bubble_ = NULL;
794 void AutofillDialogViews::OnWidgetBoundsChanged(views::Widget* widget,
795 const gfx::Rect& new_bounds) {
796 if (error_bubble_ && error_bubble_->GetWidget() == widget)
799 // Notify the web contents of its new auto-resize limits.
800 if (sign_in_delegate_ && sign_in_web_view_->visible()) {
801 sign_in_delegate_->UpdateLimitsAndEnableAutoResize(
802 GetMinimumSignInViewSize(), GetMaximumSignInViewSize());
807 bool AutofillDialogViews::NotificationArea::HasArrow() {
808 return !notifications_.empty() && notifications_[0].HasArrow() &&
809 arrow_centering_anchor_.get();
812 // AutofillDialogViews::SectionContainer ---------------------------------------
814 AutofillDialogViews::SectionContainer::SectionContainer(
815 const base::string16& label,
816 views::View* controls,
817 views::Button* proxy_button)
818 : proxy_button_(proxy_button),
819 forward_mouse_events_(false) {
820 set_notify_enter_exit_on_child(true);
822 SetBorder(views::Border::CreateEmptyBorder(kDetailSectionVerticalPadding,
824 kDetailSectionVerticalPadding,
825 kDialogEdgePadding));
827 ui::ResourceBundle& rb = ui::ResourceBundle::GetSharedInstance();
828 views::Label* label_view = new views::Label(
829 label, rb.GetFontList(ui::ResourceBundle::BoldFont));
830 label_view->SetHorizontalAlignment(gfx::ALIGN_LEFT);
832 views::View* label_bar = new views::View();
833 views::GridLayout* label_bar_layout = new views::GridLayout(label_bar);
834 label_bar->SetLayoutManager(label_bar_layout);
835 const int kColumnSetId = 0;
836 views::ColumnSet* columns = label_bar_layout->AddColumnSet(kColumnSetId);
838 views::GridLayout::LEADING,
839 views::GridLayout::LEADING,
841 views::GridLayout::FIXED,
842 kSectionContainerWidth - proxy_button->GetPreferredSize().width(),
844 columns->AddColumn(views::GridLayout::LEADING,
845 views::GridLayout::LEADING,
847 views::GridLayout::USE_PREF,
850 label_bar_layout->StartRow(0, kColumnSetId);
851 label_bar_layout->AddView(label_view);
852 label_bar_layout->AddView(proxy_button);
854 SetLayoutManager(new views::BoxLayout(views::BoxLayout::kVertical, 0, 0, 0));
855 AddChildView(label_bar);
856 AddChildView(controls);
859 scoped_ptr<views::ViewTargeter>(new views::ViewTargeter(this)));
862 AutofillDialogViews::SectionContainer::~SectionContainer() {}
864 void AutofillDialogViews::SectionContainer::SetActive(bool active) {
865 bool is_active = active && proxy_button_->visible();
866 if (is_active == !!background())
869 set_background(is_active ?
870 views::Background::CreateSolidBackground(kShadingColor) :
875 void AutofillDialogViews::SectionContainer::SetForwardMouseEvents(
877 forward_mouse_events_ = forward;
879 set_background(NULL);
882 const char* AutofillDialogViews::SectionContainer::GetClassName() const {
883 return kSectionContainerClassName;
886 void AutofillDialogViews::SectionContainer::OnMouseMoved(
887 const ui::MouseEvent& event) {
888 SetActive(ShouldForwardEvent(event));
891 void AutofillDialogViews::SectionContainer::OnMouseEntered(
892 const ui::MouseEvent& event) {
893 if (!ShouldForwardEvent(event))
897 proxy_button_->OnMouseEntered(ProxyEvent(event));
901 void AutofillDialogViews::SectionContainer::OnMouseExited(
902 const ui::MouseEvent& event) {
904 if (!ShouldForwardEvent(event))
907 proxy_button_->OnMouseExited(ProxyEvent(event));
911 bool AutofillDialogViews::SectionContainer::OnMousePressed(
912 const ui::MouseEvent& event) {
913 if (!ShouldForwardEvent(event))
916 return proxy_button_->OnMousePressed(ProxyEvent(event));
919 void AutofillDialogViews::SectionContainer::OnMouseReleased(
920 const ui::MouseEvent& event) {
921 if (!ShouldForwardEvent(event))
924 proxy_button_->OnMouseReleased(ProxyEvent(event));
927 void AutofillDialogViews::SectionContainer::OnGestureEvent(
928 ui::GestureEvent* event) {
929 if (!ShouldForwardEvent(*event))
932 proxy_button_->OnGestureEvent(event);
935 views::View* AutofillDialogViews::SectionContainer::TargetForRect(
937 const gfx::Rect& rect) {
938 CHECK_EQ(root, this);
939 views::View* handler = views::ViewTargeterDelegate::TargetForRect(root, rect);
941 // If the event is not in the label bar and there's no background to be
942 // cleared, let normal event handling take place.
944 rect.CenterPoint().y() > child_at(0)->bounds().bottom()) {
948 // Special case for (CVC) inputs in the suggestion view.
949 if (forward_mouse_events_ &&
950 handler->GetAncestorWithClassName(ExpandingTextfield::kViewClassName)) {
954 // Special case for the proxy button itself.
955 if (handler == proxy_button_)
962 ui::MouseEvent AutofillDialogViews::SectionContainer::ProxyEvent(
963 const ui::MouseEvent& event) {
964 ui::MouseEvent event_copy = event;
965 event_copy.set_location(gfx::Point());
969 bool AutofillDialogViews::SectionContainer::ShouldForwardEvent(
970 const ui::LocatedEvent& event) {
971 // Always forward events on the label bar.
972 return forward_mouse_events_ || event.y() <= child_at(0)->bounds().bottom();
975 // AutofillDialogViews::SuggestedButton ----------------------------------------
977 AutofillDialogViews::SuggestedButton::SuggestedButton(
978 views::MenuButtonListener* listener)
979 : views::MenuButton(NULL, base::string16(), listener, false) {
980 const int kFocusBorderWidth = 1;
981 SetBorder(views::Border::CreateEmptyBorder(kMenuButtonTopInset,
983 kMenuButtonBottomInset,
985 gfx::Insets insets = GetInsets();
986 insets += gfx::Insets(-kFocusBorderWidth, -kFocusBorderWidth,
987 -kFocusBorderWidth, -kFocusBorderWidth);
989 views::Painter::CreateDashedFocusPainterWithInsets(insets));
993 AutofillDialogViews::SuggestedButton::~SuggestedButton() {}
995 gfx::Size AutofillDialogViews::SuggestedButton::GetPreferredSize() const {
996 ui::ResourceBundle& rb = ui::ResourceBundle::GetSharedInstance();
997 gfx::Size size = rb.GetImageNamed(ResourceIDForState()).Size();
998 const gfx::Insets insets = GetInsets();
999 size.Enlarge(insets.width(), insets.height());
1003 const char* AutofillDialogViews::SuggestedButton::GetClassName() const {
1004 return kSuggestedButtonClassName;
1007 void AutofillDialogViews::SuggestedButton::PaintChildren(
1008 gfx::Canvas* canvas,
1009 const views::CullSet& cull_set) {
1012 void AutofillDialogViews::SuggestedButton::OnPaint(gfx::Canvas* canvas) {
1013 ui::ResourceBundle& rb = ui::ResourceBundle::GetSharedInstance();
1014 const gfx::Insets insets = GetInsets();
1015 canvas->DrawImageInt(*rb.GetImageSkiaNamed(ResourceIDForState()),
1016 insets.left(), insets.top());
1017 views::Painter::PaintFocusPainter(this, canvas, focus_painter());
1020 int AutofillDialogViews::SuggestedButton::ResourceIDForState() const {
1021 views::Button::ButtonState button_state = state();
1022 if (button_state == views::Button::STATE_PRESSED)
1023 return IDR_AUTOFILL_DIALOG_MENU_BUTTON_P;
1024 else if (button_state == views::Button::STATE_HOVERED)
1025 return IDR_AUTOFILL_DIALOG_MENU_BUTTON_H;
1026 else if (button_state == views::Button::STATE_DISABLED)
1027 return IDR_AUTOFILL_DIALOG_MENU_BUTTON_D;
1028 DCHECK_EQ(views::Button::STATE_NORMAL, button_state);
1029 return IDR_AUTOFILL_DIALOG_MENU_BUTTON;
1032 // AutofillDialogViews::DetailsContainerView -----------------------------------
1034 AutofillDialogViews::DetailsContainerView::DetailsContainerView(
1035 const base::Closure& callback)
1036 : bounds_changed_callback_(callback),
1037 ignore_layouts_(false) {}
1039 AutofillDialogViews::DetailsContainerView::~DetailsContainerView() {}
1041 void AutofillDialogViews::DetailsContainerView::OnBoundsChanged(
1042 const gfx::Rect& previous_bounds) {
1043 bounds_changed_callback_.Run();
1046 void AutofillDialogViews::DetailsContainerView::Layout() {
1047 if (!ignore_layouts_)
1048 views::View::Layout();
1051 // AutofillDialogViews::SuggestionView -----------------------------------------
1053 AutofillDialogViews::SuggestionView::SuggestionView(
1054 AutofillDialogViews* autofill_dialog)
1055 : label_(new views::Label()),
1056 label_line_2_(new views::Label()),
1057 icon_(new views::ImageView()),
1059 new ExpandingTextfield(base::string16(),
1063 // TODO(estade): Make this the correct color.
1064 SetBorder(views::Border::CreateSolidSidedBorder(1, 0, 0, 0, SK_ColorLTGRAY));
1066 SectionRowView* label_container = new SectionRowView();
1067 AddChildView(label_container);
1070 label_container->AddChildView(icon_);
1071 label_->SetHorizontalAlignment(gfx::ALIGN_LEFT);
1072 label_container->AddChildView(label_);
1074 // TODO(estade): get the sizing and spacing right on this textfield.
1075 textfield_->SetVisible(false);
1076 textfield_->SetDefaultWidthInCharacters(15);
1077 label_container->AddChildView(textfield_);
1079 label_line_2_->SetHorizontalAlignment(gfx::ALIGN_LEFT);
1080 label_line_2_->SetVisible(false);
1081 label_line_2_->SetLineHeight(22);
1082 label_line_2_->SetMultiLine(true);
1083 AddChildView(label_line_2_);
1085 SetLayoutManager(new views::BoxLayout(views::BoxLayout::kVertical, 0, 0, 7));
1088 AutofillDialogViews::SuggestionView::~SuggestionView() {}
1090 gfx::Size AutofillDialogViews::SuggestionView::GetPreferredSize() const {
1091 // There's no preferred width. The parent's layout should get the preferred
1092 // height from GetHeightForWidth().
1096 int AutofillDialogViews::SuggestionView::GetHeightForWidth(int width) const {
1098 CanUseVerticallyCompactText(width, &height);
1102 bool AutofillDialogViews::SuggestionView::CanUseVerticallyCompactText(
1103 int available_width,
1104 int* resulting_height) const {
1105 // This calculation may be costly, avoid doing it more than once per width.
1106 if (!calculated_heights_.count(available_width)) {
1107 // Changing the state of |this| now will lead to extra layouts and
1108 // paints we don't want, so create another SuggestionView to calculate
1109 // which label we have room to show.
1110 SuggestionView sizing_view(NULL);
1111 sizing_view.SetLabelText(state_.vertically_compact_text);
1112 sizing_view.SetIcon(state_.icon);
1113 sizing_view.SetTextfield(state_.extra_text, state_.extra_icon);
1114 sizing_view.label_->SetSize(gfx::Size(available_width, 0));
1115 sizing_view.label_line_2_->SetSize(gfx::Size(available_width, 0));
1117 // Shortcut |sizing_view|'s GetHeightForWidth() to avoid an infinite loop.
1118 // Its BoxLayout must do these calculations for us.
1119 views::LayoutManager* layout = sizing_view.GetLayoutManager();
1120 if (layout->GetPreferredSize(&sizing_view).width() <= available_width) {
1121 calculated_heights_[available_width] = std::make_pair(
1123 layout->GetPreferredHeightForWidth(&sizing_view, available_width));
1125 sizing_view.SetLabelText(state_.horizontally_compact_text);
1126 calculated_heights_[available_width] = std::make_pair(
1128 layout->GetPreferredHeightForWidth(&sizing_view, available_width));
1132 const std::pair<bool, int>& values = calculated_heights_[available_width];
1133 *resulting_height = values.second;
1134 return values.first;
1137 void AutofillDialogViews::SuggestionView::OnBoundsChanged(
1138 const gfx::Rect& previous_bounds) {
1142 void AutofillDialogViews::SuggestionView::SetState(
1143 const SuggestionState& state) {
1144 calculated_heights_.clear();
1146 SetVisible(state_.visible);
1148 SetIcon(state_.icon);
1149 SetTextfield(state_.extra_text, state_.extra_icon);
1150 PreferredSizeChanged();
1153 void AutofillDialogViews::SuggestionView::SetLabelText(
1154 const base::string16& text) {
1155 // TODO(estade): does this localize well?
1156 base::string16 line_return(base::ASCIIToUTF16("\n"));
1157 size_t position = text.find(line_return);
1158 if (position == base::string16::npos) {
1159 label_->SetText(text);
1160 label_line_2_->SetVisible(false);
1162 label_->SetText(text.substr(0, position));
1163 label_line_2_->SetText(text.substr(position + line_return.length()));
1164 label_line_2_->SetVisible(true);
1168 void AutofillDialogViews::SuggestionView::SetIcon(
1169 const gfx::Image& image) {
1170 icon_->SetVisible(!image.IsEmpty());
1171 icon_->SetImage(image.AsImageSkia());
1174 void AutofillDialogViews::SuggestionView::SetTextfield(
1175 const base::string16& placeholder_text,
1176 const gfx::Image& icon) {
1177 textfield_->SetPlaceholderText(placeholder_text);
1178 textfield_->SetIcon(icon);
1179 textfield_->SetVisible(!placeholder_text.empty());
1182 void AutofillDialogViews::SuggestionView::UpdateLabelText() {
1184 SetLabelText(CanUseVerticallyCompactText(width(), &unused) ?
1185 state_.vertically_compact_text :
1186 state_.horizontally_compact_text);
1189 // AutofillDialogView ----------------------------------------------------------
1192 AutofillDialogView* AutofillDialogView::Create(
1193 AutofillDialogViewDelegate* delegate) {
1194 return new AutofillDialogViews(delegate);
1197 // AutofillDialogViews ---------------------------------------------------------
1199 AutofillDialogViews::AutofillDialogViews(AutofillDialogViewDelegate* delegate)
1200 : delegate_(delegate),
1202 needs_update_(false),
1204 notification_area_(NULL),
1205 account_chooser_(NULL),
1206 sign_in_web_view_(NULL),
1207 scrollable_area_(NULL),
1208 details_container_(NULL),
1209 loading_shield_(NULL),
1210 loading_shield_height_(0),
1211 overlay_view_(NULL),
1212 button_strip_extra_view_(NULL),
1213 save_in_chrome_checkbox_(NULL),
1214 save_in_chrome_checkbox_container_(NULL),
1215 button_strip_image_(NULL),
1216 footnote_view_(NULL),
1217 legal_document_view_(NULL),
1218 focus_manager_(NULL),
1219 error_bubble_(NULL),
1222 detail_groups_.insert(std::make_pair(SECTION_CC,
1223 DetailsGroup(SECTION_CC)));
1224 detail_groups_.insert(std::make_pair(SECTION_BILLING,
1225 DetailsGroup(SECTION_BILLING)));
1226 detail_groups_.insert(std::make_pair(SECTION_CC_BILLING,
1227 DetailsGroup(SECTION_CC_BILLING)));
1228 detail_groups_.insert(std::make_pair(SECTION_SHIPPING,
1229 DetailsGroup(SECTION_SHIPPING)));
1232 AutofillDialogViews::~AutofillDialogViews() {
1237 void AutofillDialogViews::Show() {
1239 UpdateAccountChooser();
1240 UpdateNotificationArea();
1241 UpdateButtonStripExtraView();
1243 window_ = ShowWebModalDialogViews(this, delegate_->GetWebContents());
1244 focus_manager_ = window_->GetFocusManager();
1245 focus_manager_->AddFocusChangeListener(this);
1247 ShowDialogInMode(DETAIL_INPUT);
1249 // Listen for size changes on the browser.
1250 views::Widget* browser_widget =
1251 views::Widget::GetTopLevelWidgetForNativeView(
1252 delegate_->GetWebContents()->GetNativeView());
1253 observer_.Add(browser_widget);
1255 // Listen for unhandled mouse presses on the non-client view.
1256 event_handler_.reset(new MousePressedHandler(delegate_));
1257 window_->GetRootView()->AddPostTargetHandler(event_handler_.get());
1258 observer_.Add(window_);
1261 void AutofillDialogViews::Hide() {
1266 void AutofillDialogViews::UpdatesStarted() {
1270 void AutofillDialogViews::UpdatesFinished() {
1272 DCHECK_GE(updates_scope_, 0);
1273 if (updates_scope_ == 0 && needs_update_) {
1274 needs_update_ = false;
1275 ContentsPreferredSizeChanged();
1279 void AutofillDialogViews::UpdateAccountChooser() {
1280 account_chooser_->Update();
1282 bool show_loading = delegate_->ShouldShowSpinner();
1283 if (show_loading != loading_shield_->visible()) {
1285 loading_shield_height_ = std::max(kInitialLoadingShieldHeight,
1286 GetContentsBounds().height());
1287 ShowDialogInMode(LOADING);
1289 bool show_sign_in = delegate_->ShouldShowSignInWebView();
1290 ShowDialogInMode(show_sign_in ? SIGN_IN : DETAIL_INPUT);
1294 ContentsPreferredSizeChanged();
1297 // Update legal documents for the account.
1298 if (footnote_view_) {
1299 const base::string16 text = delegate_->LegalDocumentsText();
1300 legal_document_view_->SetText(text);
1302 if (!text.empty()) {
1303 const std::vector<gfx::Range>& link_ranges =
1304 delegate_->LegalDocumentLinks();
1305 for (size_t i = 0; i < link_ranges.size(); ++i) {
1306 views::StyledLabel::RangeStyleInfo link_range_info =
1307 views::StyledLabel::RangeStyleInfo::CreateForLink();
1308 link_range_info.disable_line_wrapping = false;
1309 legal_document_view_->AddStyleRange(link_ranges[i], link_range_info);
1313 footnote_view_->SetVisible(!text.empty());
1314 ContentsPreferredSizeChanged();
1318 GetWidget()->UpdateWindowTitle();
1321 void AutofillDialogViews::UpdateButtonStrip() {
1322 button_strip_extra_view_->SetVisible(
1323 GetDialogButtons() != ui::DIALOG_BUTTON_NONE);
1324 UpdateButtonStripExtraView();
1325 GetDialogClientView()->UpdateDialogButtons();
1327 ContentsPreferredSizeChanged();
1330 void AutofillDialogViews::UpdateOverlay() {
1331 overlay_view_->UpdateState();
1332 ContentsPreferredSizeChanged();
1335 void AutofillDialogViews::UpdateDetailArea() {
1336 scrollable_area_->SetVisible(true);
1337 ContentsPreferredSizeChanged();
1340 void AutofillDialogViews::UpdateForErrors() {
1344 void AutofillDialogViews::UpdateNotificationArea() {
1345 DCHECK(notification_area_);
1346 notification_area_->SetNotifications(delegate_->CurrentNotifications());
1347 ContentsPreferredSizeChanged();
1350 void AutofillDialogViews::UpdateSection(DialogSection section) {
1351 UpdateSectionImpl(section, true);
1354 void AutofillDialogViews::UpdateErrorBubble() {
1355 if (!delegate_->ShouldShowErrorBubble())
1359 void AutofillDialogViews::FillSection(DialogSection section,
1360 ServerFieldType originating_type) {
1361 DetailsGroup* group = GroupForSection(section);
1362 // Make sure to overwrite the originating input if it exists.
1363 TextfieldMap::iterator text_mapping =
1364 group->textfields.find(originating_type);
1365 if (text_mapping != group->textfields.end())
1366 text_mapping->second->SetText(base::string16());
1368 // If the Autofill data comes from a credit card, make sure to overwrite the
1369 // CC comboboxes (even if they already have something in them). If the
1370 // Autofill data comes from an AutofillProfile, leave the comboboxes alone.
1371 if (section == GetCreditCardSection() &&
1372 AutofillType(originating_type).group() == CREDIT_CARD) {
1373 for (ComboboxMap::const_iterator it = group->comboboxes.begin();
1374 it != group->comboboxes.end(); ++it) {
1375 if (AutofillType(it->first).group() == CREDIT_CARD)
1376 it->second->SetSelectedIndex(it->second->model()->GetDefaultIndex());
1380 UpdateSectionImpl(section, false);
1383 void AutofillDialogViews::GetUserInput(DialogSection section,
1384 FieldValueMap* output) {
1385 DetailsGroup* group = GroupForSection(section);
1386 for (TextfieldMap::const_iterator it = group->textfields.begin();
1387 it != group->textfields.end(); ++it) {
1388 output->insert(std::make_pair(it->first, it->second->GetText()));
1390 for (ComboboxMap::const_iterator it = group->comboboxes.begin();
1391 it != group->comboboxes.end(); ++it) {
1392 output->insert(std::make_pair(it->first,
1393 it->second->model()->GetItemAt(it->second->selected_index())));
1397 base::string16 AutofillDialogViews::GetCvc() {
1398 return GroupForSection(GetCreditCardSection())->suggested_info->
1399 textfield()->GetText();
1402 bool AutofillDialogViews::SaveDetailsLocally() {
1403 DCHECK(save_in_chrome_checkbox_->visible());
1404 return save_in_chrome_checkbox_->checked();
1407 const content::NavigationController* AutofillDialogViews::ShowSignIn() {
1408 // The initial minimum width and height are set such that the dialog
1409 // won't change size before the page is loaded.
1410 int min_width = GetContentsBounds().width();
1411 // The height has to include the button strip.
1412 int min_height = GetDialogClientView()->GetContentsBounds().height();
1414 // TODO(abodenha): We should be able to use the WebContents of the WebView
1415 // to navigate instead of LoadInitialURL. Figure out why it doesn't work.
1416 sign_in_delegate_.reset(
1417 new AutofillDialogSignInDelegate(
1419 sign_in_web_view_->GetWebContents(),
1420 delegate_->GetWebContents(),
1421 gfx::Size(min_width, min_height), GetMaximumSignInViewSize()));
1422 sign_in_web_view_->LoadInitialURL(delegate_->SignInUrl());
1424 ShowDialogInMode(SIGN_IN);
1426 ContentsPreferredSizeChanged();
1428 return &sign_in_web_view_->web_contents()->GetController();
1431 void AutofillDialogViews::HideSignIn() {
1432 sign_in_web_view_->SetWebContents(NULL);
1434 if (delegate_->ShouldShowSpinner()) {
1435 UpdateAccountChooser();
1437 ShowDialogInMode(DETAIL_INPUT);
1440 DCHECK(!sign_in_web_view_->visible());
1442 ContentsPreferredSizeChanged();
1445 void AutofillDialogViews::ModelChanged() {
1446 menu_runner_.reset();
1448 for (DetailGroupMap::const_iterator iter = detail_groups_.begin();
1449 iter != detail_groups_.end(); ++iter) {
1450 UpdateDetailsGroupState(iter->second);
1454 void AutofillDialogViews::OnSignInResize(const gfx::Size& pref_size) {
1455 sign_in_web_view_->SetPreferredSize(pref_size);
1456 ContentsPreferredSizeChanged();
1459 void AutofillDialogViews::ValidateSection(DialogSection section) {
1460 ValidateGroup(*GroupForSection(section), VALIDATE_EDIT);
1463 gfx::Size AutofillDialogViews::GetPreferredSize() const {
1464 if (preferred_size_.IsEmpty())
1465 preferred_size_ = CalculatePreferredSize(false);
1467 return preferred_size_;
1470 gfx::Size AutofillDialogViews::GetMinimumSize() const {
1471 return CalculatePreferredSize(true);
1474 void AutofillDialogViews::Layout() {
1475 const gfx::Rect content_bounds = GetContentsBounds();
1476 if (sign_in_web_view_->visible()) {
1477 sign_in_web_view_->SetBoundsRect(content_bounds);
1481 if (loading_shield_->visible()) {
1482 loading_shield_->SetBoundsRect(bounds());
1486 const int x = content_bounds.x();
1487 const int y = content_bounds.y();
1488 const int width = content_bounds.width();
1489 // Layout notification area at top of dialog.
1490 int notification_height = notification_area_->GetHeightForWidth(width);
1491 notification_area_->SetBounds(x, y, width, notification_height);
1493 // The rest (the |scrollable_area_|) takes up whatever's left.
1494 if (scrollable_area_->visible()) {
1496 if (notification_height > notification_area_->GetInsets().height())
1497 scroll_y += notification_height + views::kRelatedControlVerticalSpacing;
1499 int scroll_bottom = content_bounds.bottom();
1500 DCHECK_EQ(scrollable_area_->contents(), details_container_);
1501 details_container_->SizeToPreferredSize();
1502 details_container_->Layout();
1503 // TODO(estade): remove this hack. See crbug.com/285996
1504 details_container_->set_ignore_layouts(true);
1505 scrollable_area_->SetBounds(x, scroll_y, width, scroll_bottom - scroll_y);
1506 details_container_->set_ignore_layouts(false);
1510 error_bubble_->UpdatePosition();
1513 void AutofillDialogViews::OnNativeThemeChanged(
1514 const ui::NativeTheme* theme) {
1515 if (!legal_document_view_)
1518 // NOTE: This color may change because of |auto_color_readability|, set on
1519 // |legal_document_view_|.
1520 views::StyledLabel::RangeStyleInfo default_style;
1521 default_style.color =
1522 theme->GetSystemColor(ui::NativeTheme::kColorId_LabelDisabledColor);
1524 legal_document_view_->SetDefaultStyle(default_style);
1527 ui::ModalType AutofillDialogViews::GetModalType() const {
1528 return ui::MODAL_TYPE_CHILD;
1531 base::string16 AutofillDialogViews::GetWindowTitle() const {
1532 base::string16 title = delegate_->DialogTitle();
1533 // Hack alert: we don't want the dialog to jiggle when a title is added or
1534 // removed. Setting a non-empty string here keeps the dialog's title bar the
1536 return title.empty() ? base::ASCIIToUTF16(" ") : title;
1539 void AutofillDialogViews::WindowClosing() {
1540 focus_manager_->RemoveFocusChangeListener(this);
1543 void AutofillDialogViews::DeleteDelegate() {
1545 // |this| belongs to the controller (|delegate_|).
1546 delegate_->ViewClosed();
1549 int AutofillDialogViews::GetDialogButtons() const {
1550 return delegate_->GetDialogButtons();
1553 int AutofillDialogViews::GetDefaultDialogButton() const {
1554 if (GetDialogButtons() & ui::DIALOG_BUTTON_OK)
1555 return ui::DIALOG_BUTTON_OK;
1557 return ui::DIALOG_BUTTON_NONE;
1560 base::string16 AutofillDialogViews::GetDialogButtonLabel(
1561 ui::DialogButton button) const {
1562 return button == ui::DIALOG_BUTTON_OK ?
1563 delegate_->ConfirmButtonText() : delegate_->CancelButtonText();
1566 bool AutofillDialogViews::ShouldDefaultButtonBeBlue() const {
1570 bool AutofillDialogViews::IsDialogButtonEnabled(ui::DialogButton button) const {
1571 return delegate_->IsDialogButtonEnabled(button);
1574 views::View* AutofillDialogViews::GetInitiallyFocusedView() {
1575 if (!window_ || !focus_manager_)
1578 if (sign_in_web_view_->visible())
1579 return sign_in_web_view_;
1581 if (loading_shield_->visible())
1582 return views::DialogDelegateView::GetInitiallyFocusedView();
1584 DCHECK(scrollable_area_->visible());
1586 views::FocusManager* manager = focus_manager_;
1587 for (views::View* next = scrollable_area_;
1589 next = manager->GetNextFocusableView(next, window_, false, true)) {
1590 views::View* input_view = GetAncestralInputView(next);
1594 // If there are no invalid inputs, return the first input found. Otherwise,
1595 // return the first invalid input found.
1596 if (validity_map_.empty() ||
1597 validity_map_.find(input_view) != validity_map_.end()) {
1602 return views::DialogDelegateView::GetInitiallyFocusedView();
1605 views::View* AutofillDialogViews::CreateExtraView() {
1606 return button_strip_extra_view_;
1609 views::View* AutofillDialogViews::CreateTitlebarExtraView() {
1610 return account_chooser_;
1613 views::View* AutofillDialogViews::CreateFootnoteView() {
1614 footnote_view_ = new LayoutPropagationView();
1615 footnote_view_->SetLayoutManager(
1616 new views::BoxLayout(views::BoxLayout::kVertical,
1620 footnote_view_->SetBorder(
1621 views::Border::CreateSolidSidedBorder(1, 0, 0, 0, kSubtleBorderColor));
1622 footnote_view_->set_background(
1623 views::Background::CreateSolidBackground(kShadingColor));
1625 legal_document_view_ = new views::StyledLabel(base::string16(), this);
1627 footnote_view_->AddChildView(legal_document_view_);
1628 footnote_view_->SetVisible(false);
1630 return footnote_view_;
1633 views::View* AutofillDialogViews::CreateOverlayView() {
1634 return overlay_view_;
1637 bool AutofillDialogViews::Cancel() {
1638 return delegate_->OnCancel();
1641 bool AutofillDialogViews::Accept() {
1643 return delegate_->OnAccept();
1645 // |ValidateForm()| failed; there should be invalid views in |validity_map_|.
1646 DCHECK(!validity_map_.empty());
1652 void AutofillDialogViews::ContentsChanged(views::Textfield* sender,
1653 const base::string16& new_contents) {
1654 InputEditedOrActivated(TypeForTextfield(sender),
1655 sender->GetBoundsInScreen(),
1658 const ExpandingTextfield* expanding = static_cast<ExpandingTextfield*>(
1659 sender->GetAncestorWithClassName(ExpandingTextfield::kViewClassName));
1660 if (expanding && expanding->needs_layout())
1661 ContentsPreferredSizeChanged();
1664 bool AutofillDialogViews::HandleKeyEvent(views::Textfield* sender,
1665 const ui::KeyEvent& key_event) {
1666 ui::KeyEvent copy(key_event);
1667 content::NativeWebKeyboardEvent event(©);
1668 return delegate_->HandleKeyPressEventInInput(event);
1671 bool AutofillDialogViews::HandleMouseEvent(views::Textfield* sender,
1672 const ui::MouseEvent& mouse_event) {
1673 if (mouse_event.IsLeftMouseButton() && sender->HasFocus()) {
1674 InputEditedOrActivated(TypeForTextfield(sender),
1675 sender->GetBoundsInScreen(),
1677 // Show an error bubble if a user clicks on an input that's already focused
1679 ShowErrorBubbleForViewIfNecessary(sender);
1685 void AutofillDialogViews::OnWillChangeFocus(
1686 views::View* focused_before,
1687 views::View* focused_now) {
1688 delegate_->FocusMoved();
1692 void AutofillDialogViews::OnDidChangeFocus(
1693 views::View* focused_before,
1694 views::View* focused_now) {
1695 // If user leaves an edit-field, revalidate the group it belongs to.
1696 if (focused_before) {
1697 DetailsGroup* group = GroupForView(focused_before);
1698 if (group && group->container->visible())
1699 ValidateGroup(*group, VALIDATE_EDIT);
1702 // Show an error bubble when the user focuses the input.
1704 focused_now->ScrollRectToVisible(focused_now->GetLocalBounds());
1705 ShowErrorBubbleForViewIfNecessary(focused_now);
1709 void AutofillDialogViews::OnPerformAction(views::Combobox* combobox) {
1710 DialogSection section = GroupForView(combobox)->section;
1711 InputEditedOrActivated(TypeForCombobox(combobox), gfx::Rect(), true);
1712 // NOTE: |combobox| may have been deleted.
1713 ValidateGroup(*GroupForSection(section), VALIDATE_EDIT);
1714 SetEditabilityForSection(section);
1717 void AutofillDialogViews::StyledLabelLinkClicked(const gfx::Range& range,
1719 delegate_->LegalDocumentLinkClicked(range);
1722 void AutofillDialogViews::OnMenuButtonClicked(views::View* source,
1723 const gfx::Point& point) {
1724 DCHECK_EQ(kSuggestedButtonClassName, source->GetClassName());
1726 DetailsGroup* group = NULL;
1727 for (DetailGroupMap::iterator iter = detail_groups_.begin();
1728 iter != detail_groups_.end(); ++iter) {
1729 if (source == iter->second.suggested_button) {
1730 group = &iter->second;
1736 if (!group->suggested_button->visible())
1740 new views::MenuRunner(delegate_->MenuModelForSection(group->section), 0));
1742 group->container->SetActive(true);
1743 views::Button::ButtonState state = group->suggested_button->state();
1744 group->suggested_button->SetState(views::Button::STATE_PRESSED);
1746 gfx::Rect screen_bounds = source->GetBoundsInScreen();
1747 screen_bounds.Inset(source->GetInsets());
1748 if (menu_runner_->RunMenuAt(source->GetWidget(),
1751 views::MENU_ANCHOR_TOPRIGHT,
1752 ui::MENU_SOURCE_NONE) ==
1753 views::MenuRunner::MENU_DELETED) {
1757 group->container->SetActive(false);
1758 group->suggested_button->SetState(state);
1761 gfx::Size AutofillDialogViews::CalculatePreferredSize(
1762 bool get_minimum_size) const {
1763 gfx::Insets insets = GetInsets();
1764 gfx::Size scroll_size = scrollable_area_->contents()->GetPreferredSize();
1765 // The width is always set by the scroll area.
1766 const int width = scroll_size.width();
1768 if (sign_in_web_view_->visible()) {
1769 const gfx::Size size = static_cast<views::View*>(sign_in_web_view_)->
1771 return gfx::Size(width + insets.width(), size.height() + insets.height());
1774 if (overlay_view_->visible()) {
1775 const int height = overlay_view_->GetHeightForContentsForWidth(width);
1777 return gfx::Size(width + insets.width(), height + insets.height());
1780 if (loading_shield_->visible()) {
1781 return gfx::Size(width + insets.width(),
1782 loading_shield_height_ + insets.height());
1786 const int notification_height = notification_area_->GetHeightForWidth(width);
1787 if (notification_height > notification_area_->GetInsets().height())
1788 height += notification_height + views::kRelatedControlVerticalSpacing;
1790 if (scrollable_area_->visible())
1791 height += get_minimum_size ? kMinimumContentsHeight : scroll_size.height();
1793 return gfx::Size(width + insets.width(), height + insets.height());
1796 gfx::Size AutofillDialogViews::GetMinimumSignInViewSize() const {
1797 return gfx::Size(GetDialogClientView()->size().width() - GetInsets().width(),
1798 kMinimumContentsHeight);
1801 gfx::Size AutofillDialogViews::GetMaximumSignInViewSize() const {
1802 web_modal::WebContentsModalDialogHost* dialog_host =
1803 web_modal::WebContentsModalDialogManager::FromWebContents(
1804 delegate_->GetWebContents())->delegate()->
1805 GetWebContentsModalDialogHost();
1807 // Inset the maximum dialog height to get the maximum content height.
1808 int height = dialog_host->GetMaximumDialogSize().height();
1809 const int non_client_height = GetWidget()->non_client_view()->height();
1810 const int client_height = GetWidget()->client_view()->height();
1811 // TODO(msw): Resolve the 12 pixel discrepancy; is that the bubble border?
1812 height -= non_client_height - client_height - 12;
1813 height = std::max(height, kMinimumContentsHeight);
1815 // The dialog's width never changes.
1816 const int width = GetDialogClientView()->size().width() - GetInsets().width();
1817 return gfx::Size(width, height);
1820 DialogSection AutofillDialogViews::GetCreditCardSection() const {
1821 if (delegate_->SectionIsActive(SECTION_CC))
1824 DCHECK(delegate_->SectionIsActive(SECTION_CC_BILLING));
1825 return SECTION_CC_BILLING;
1828 void AutofillDialogViews::InitChildViews() {
1829 button_strip_extra_view_ = new LayoutPropagationView();
1830 button_strip_extra_view_->SetLayoutManager(
1831 new views::BoxLayout(views::BoxLayout::kHorizontal, 0, 0, 0));
1833 save_in_chrome_checkbox_container_ = new views::View();
1834 save_in_chrome_checkbox_container_->SetLayoutManager(
1835 new views::BoxLayout(views::BoxLayout::kHorizontal, 0, 0, 7));
1836 button_strip_extra_view_->AddChildView(save_in_chrome_checkbox_container_);
1838 save_in_chrome_checkbox_ =
1839 new views::Checkbox(delegate_->SaveLocallyText());
1840 save_in_chrome_checkbox_->SetChecked(delegate_->ShouldSaveInChrome());
1841 save_in_chrome_checkbox_container_->AddChildView(save_in_chrome_checkbox_);
1843 save_in_chrome_checkbox_container_->AddChildView(
1844 new TooltipIcon(delegate_->SaveLocallyTooltip()));
1846 button_strip_image_ = new views::ImageView();
1847 button_strip_extra_view_->AddChildView(button_strip_image_);
1849 account_chooser_ = new AccountChooser(delegate_);
1850 notification_area_ = new NotificationArea(delegate_);
1851 notification_area_->set_arrow_centering_anchor(account_chooser_->AsWeakPtr());
1852 AddChildView(notification_area_);
1854 scrollable_area_ = new views::ScrollView();
1855 scrollable_area_->set_hide_horizontal_scrollbar(true);
1856 scrollable_area_->SetContents(CreateDetailsContainer());
1857 AddChildView(scrollable_area_);
1859 loading_shield_ = new LoadingAnimationView(delegate_->SpinnerText());
1860 AddChildView(loading_shield_);
1862 sign_in_web_view_ = new views::WebView(delegate_->profile());
1863 AddChildView(sign_in_web_view_);
1865 overlay_view_ = new OverlayView(delegate_);
1866 overlay_view_->SetVisible(false);
1869 views::View* AutofillDialogViews::CreateDetailsContainer() {
1870 details_container_ = new DetailsContainerView(
1871 base::Bind(&AutofillDialogViews::DetailsContainerBoundsChanged,
1872 base::Unretained(this)));
1874 // A box layout is used because it respects widget visibility.
1875 details_container_->SetLayoutManager(
1876 new views::BoxLayout(views::BoxLayout::kVertical, 0, 0, 0));
1877 for (DetailGroupMap::iterator iter = detail_groups_.begin();
1878 iter != detail_groups_.end(); ++iter) {
1879 CreateDetailsSection(iter->second.section);
1880 details_container_->AddChildView(iter->second.container);
1883 return details_container_;
1886 void AutofillDialogViews::CreateDetailsSection(DialogSection section) {
1887 // Inputs container (manual inputs + combobox).
1888 views::View* inputs_container = CreateInputsContainer(section);
1890 DetailsGroup* group = GroupForSection(section);
1891 // Container (holds label + inputs).
1892 group->container = new SectionContainer(delegate_->LabelForSection(section),
1894 group->suggested_button);
1895 DCHECK(group->suggested_button->parent());
1896 UpdateDetailsGroupState(*group);
1899 views::View* AutofillDialogViews::CreateInputsContainer(DialogSection section) {
1900 // The |info_view| holds |manual_inputs| and |suggested_info|, allowing the
1901 // dialog to toggle which is shown.
1902 views::View* info_view = new views::View();
1903 info_view->SetLayoutManager(
1904 new views::BoxLayout(views::BoxLayout::kVertical, 0, 0, 0));
1906 DetailsGroup* group = GroupForSection(section);
1907 group->manual_input = new views::View();
1908 InitInputsView(section);
1909 info_view->AddChildView(group->manual_input);
1911 group->suggested_info = new SuggestionView(this);
1912 info_view->AddChildView(group->suggested_info);
1914 // TODO(estade): It might be slightly more OO if this button were created
1915 // and listened to by the section container.
1916 group->suggested_button = new SuggestedButton(this);
1921 // TODO(estade): we should be using Chrome-style constrained window padding
1923 void AutofillDialogViews::InitInputsView(DialogSection section) {
1924 DetailsGroup* group = GroupForSection(section);
1925 EraseInvalidViewsInGroup(group);
1927 TextfieldMap* textfields = &group->textfields;
1928 textfields->clear();
1930 ComboboxMap* comboboxes = &group->comboboxes;
1931 comboboxes->clear();
1933 views::View* view = group->manual_input;
1934 view->RemoveAllChildViews(true);
1936 views::GridLayout* layout = new views::GridLayout(view);
1937 view->SetLayoutManager(layout);
1939 int column_set_id = 0;
1940 const DetailInputs& inputs = delegate_->RequestedFieldsForSection(section);
1941 for (DetailInputs::const_iterator it = inputs.begin();
1942 it != inputs.end(); ++it) {
1943 const DetailInput& input = *it;
1945 ui::ComboboxModel* input_model =
1946 delegate_->ComboboxModelForAutofillType(input.type);
1947 scoped_ptr<views::View> view_to_add;
1949 views::Combobox* combobox = new views::Combobox(input_model);
1950 combobox->set_listener(this);
1951 comboboxes->insert(std::make_pair(input.type, combobox));
1952 SelectComboboxValueOrSetToDefault(combobox, input.initial_value);
1953 view_to_add.reset(combobox);
1955 ExpandingTextfield* field = new ExpandingTextfield(input.initial_value,
1956 input.placeholder_text,
1957 input.IsMultiline(),
1959 textfields->insert(std::make_pair(input.type, field));
1960 view_to_add.reset(field);
1963 if (input.length == DetailInput::NONE) {
1964 other_owned_views_.push_back(view_to_add.release());
1968 if (input.length == DetailInput::LONG)
1971 views::ColumnSet* column_set = layout->GetColumnSet(column_set_id);
1973 // Create a new column set and row.
1974 column_set = layout->AddColumnSet(column_set_id);
1975 if (it != inputs.begin())
1976 layout->AddPaddingRow(0, kManualInputRowPadding);
1977 layout->StartRow(0, column_set_id);
1979 // Add a new column to existing row.
1980 column_set->AddPaddingColumn(0, views::kRelatedControlHorizontalSpacing);
1981 // Must explicitly skip the padding column since we've already started
1983 layout->SkipColumns(1);
1986 float expand = input.expand_weight;
1987 column_set->AddColumn(views::GridLayout::FILL,
1988 views::GridLayout::FILL,
1989 expand ? expand : 1.0,
1990 views::GridLayout::USE_PREF,
1994 // This is the same as AddView(view_to_add), except that 1 is used for the
1995 // view's preferred width. Thus the width of the column completely depends
1997 layout->AddView(view_to_add.release(), 1, 1,
1998 views::GridLayout::FILL, views::GridLayout::FILL,
2001 if (input.length == DetailInput::LONG ||
2002 input.length == DetailInput::SHORT_EOL) {
2007 SetIconsForSection(section);
2010 void AutofillDialogViews::ShowDialogInMode(DialogMode dialog_mode) {
2011 loading_shield_->SetVisible(dialog_mode == LOADING);
2012 sign_in_web_view_->SetVisible(dialog_mode == SIGN_IN);
2013 notification_area_->SetVisible(dialog_mode == DETAIL_INPUT);
2014 scrollable_area_->SetVisible(dialog_mode == DETAIL_INPUT);
2018 void AutofillDialogViews::UpdateSectionImpl(
2019 DialogSection section,
2020 bool clobber_inputs) {
2021 DetailsGroup* group = GroupForSection(section);
2023 if (clobber_inputs) {
2024 ServerFieldType type = UNKNOWN_TYPE;
2025 views::View* focused = GetFocusManager()->GetFocusedView();
2026 if (focused && group->container->Contains(focused)) {
2027 // Remember which view was focused before the inputs are clobbered.
2028 if (focused->GetClassName() == ExpandingTextfield::kViewClassName)
2029 type = TypeForTextfield(focused);
2030 else if (focused->GetClassName() == views::Combobox::kViewClassName)
2031 type = TypeForCombobox(static_cast<views::Combobox*>(focused));
2034 InitInputsView(section);
2036 if (type != UNKNOWN_TYPE) {
2037 // Restore the focus to the input with the previous type (e.g. country).
2038 views::View* to_focus = TextfieldForType(type);
2039 if (!to_focus) to_focus = ComboboxForType(type);
2041 to_focus->RequestFocus();
2044 const DetailInputs& updated_inputs =
2045 delegate_->RequestedFieldsForSection(section);
2047 for (DetailInputs::const_iterator iter = updated_inputs.begin();
2048 iter != updated_inputs.end(); ++iter) {
2049 const DetailInput& input = *iter;
2051 TextfieldMap::iterator text_mapping = group->textfields.find(input.type);
2052 if (text_mapping != group->textfields.end()) {
2053 ExpandingTextfield* textfield = text_mapping->second;
2054 if (textfield->GetText().empty())
2055 textfield->SetText(input.initial_value);
2058 ComboboxMap::iterator combo_mapping = group->comboboxes.find(input.type);
2059 if (combo_mapping != group->comboboxes.end()) {
2060 views::Combobox* combobox = combo_mapping->second;
2061 if (combobox->selected_index() == combobox->model()->GetDefaultIndex())
2062 SelectComboboxValueOrSetToDefault(combobox, input.initial_value);
2066 SetIconsForSection(section);
2069 SetEditabilityForSection(section);
2070 UpdateDetailsGroupState(*group);
2073 void AutofillDialogViews::UpdateDetailsGroupState(const DetailsGroup& group) {
2074 const SuggestionState& suggestion_state =
2075 delegate_->SuggestionStateForSection(group.section);
2076 group.suggested_info->SetState(suggestion_state);
2077 group.manual_input->SetVisible(!suggestion_state.visible);
2079 UpdateButtonStripExtraView();
2081 const bool has_menu = !!delegate_->MenuModelForSection(group.section);
2083 if (group.suggested_button)
2084 group.suggested_button->SetVisible(has_menu);
2086 if (group.container) {
2087 group.container->SetForwardMouseEvents(
2088 has_menu && suggestion_state.visible);
2089 group.container->SetVisible(delegate_->SectionIsActive(group.section));
2090 if (group.container->visible())
2091 ValidateGroup(group, VALIDATE_EDIT);
2094 ContentsPreferredSizeChanged();
2097 void AutofillDialogViews::FocusInitialView() {
2098 views::View* to_focus = GetInitiallyFocusedView();
2099 if (to_focus && !to_focus->HasFocus())
2100 to_focus->RequestFocus();
2104 void AutofillDialogViews::SetValidityForInput(
2106 const base::string16& message) {
2107 bool invalid = !message.empty();
2108 input->SetInvalid(invalid);
2111 validity_map_[input] = message;
2113 validity_map_.erase(input);
2115 if (error_bubble_ &&
2116 error_bubble_->anchor()->GetAncestorWithClassName(
2117 input->GetClassName()) == input) {
2118 validity_map_.erase(input);
2124 void AutofillDialogViews::ShowErrorBubbleForViewIfNecessary(views::View* view) {
2125 if (!view->GetWidget())
2128 if (!delegate_->ShouldShowErrorBubble()) {
2129 DCHECK(!error_bubble_);
2133 if (view->GetClassName() == DecoratedTextfield::kViewClassName &&
2134 !static_cast<DecoratedTextfield*>(view)->invalid()) {
2138 views::View* input_view = GetAncestralInputView(view);
2139 std::map<views::View*, base::string16>::iterator error_message =
2140 validity_map_.find(input_view);
2141 if (error_message != validity_map_.end()) {
2142 input_view->ScrollRectToVisible(input_view->GetLocalBounds());
2144 if (!error_bubble_ || error_bubble_->anchor() != view) {
2146 error_bubble_ = new InfoBubble(view, error_message->second);
2147 error_bubble_->set_align_to_anchor_edge(true);
2148 error_bubble_->set_preferred_width(
2149 (kSectionContainerWidth - views::kRelatedControlVerticalSpacing) / 2);
2150 bool show_above = view->GetClassName() == views::Combobox::kViewClassName;
2151 error_bubble_->set_show_above_anchor(show_above);
2152 error_bubble_->Show();
2153 observer_.Add(error_bubble_->GetWidget());
2158 void AutofillDialogViews::HideErrorBubble() {
2160 error_bubble_->Hide();
2163 void AutofillDialogViews::MarkInputsInvalid(
2164 DialogSection section,
2165 const ValidityMessages& messages,
2166 bool overwrite_unsure) {
2167 DetailsGroup* group = GroupForSection(section);
2168 DCHECK(group->container->visible());
2170 if (group->manual_input->visible()) {
2171 for (TextfieldMap::const_iterator iter = group->textfields.begin();
2172 iter != group->textfields.end(); ++iter) {
2173 const ValidityMessage& message =
2174 messages.GetMessageOrDefault(iter->first);
2175 if (overwrite_unsure || message.sure)
2176 SetValidityForInput(iter->second, message.text);
2178 for (ComboboxMap::const_iterator iter = group->comboboxes.begin();
2179 iter != group->comboboxes.end(); ++iter) {
2180 const ValidityMessage& message =
2181 messages.GetMessageOrDefault(iter->first);
2182 if (overwrite_unsure || message.sure)
2183 SetValidityForInput(iter->second, message.text);
2186 EraseInvalidViewsInGroup(group);
2188 if (section == GetCreditCardSection()) {
2189 // Special case CVC as it's not part of |group->manual_input|.
2190 const ValidityMessage& message =
2191 messages.GetMessageOrDefault(CREDIT_CARD_VERIFICATION_CODE);
2192 if (overwrite_unsure || message.sure) {
2193 SetValidityForInput(group->suggested_info->textfield(), message.text);
2199 bool AutofillDialogViews::ValidateGroup(const DetailsGroup& group,
2200 ValidationType validation_type) {
2201 DCHECK(group.container->visible());
2203 FieldValueMap detail_outputs;
2205 if (group.manual_input->visible()) {
2206 for (TextfieldMap::const_iterator iter = group.textfields.begin();
2207 iter != group.textfields.end(); ++iter) {
2208 if (!iter->second->editable())
2211 detail_outputs[iter->first] = iter->second->GetText();
2213 for (ComboboxMap::const_iterator iter = group.comboboxes.begin();
2214 iter != group.comboboxes.end(); ++iter) {
2215 if (!iter->second->enabled())
2218 views::Combobox* combobox = iter->second;
2219 base::string16 item =
2220 combobox->model()->GetItemAt(combobox->selected_index());
2221 detail_outputs[iter->first] = item;
2223 } else if (group.section == GetCreditCardSection()) {
2224 ExpandingTextfield* cvc = group.suggested_info->textfield();
2226 detail_outputs[CREDIT_CARD_VERIFICATION_CODE] = cvc->GetText();
2229 ValidityMessages validity = delegate_->InputsAreValid(group.section,
2231 MarkInputsInvalid(group.section, validity, validation_type == VALIDATE_FINAL);
2233 // If there are any validation errors, sure or unsure, the group is invalid.
2234 return !validity.HasErrors();
2237 bool AutofillDialogViews::ValidateForm() {
2238 bool all_valid = true;
2239 validity_map_.clear();
2241 for (DetailGroupMap::iterator iter = detail_groups_.begin();
2242 iter != detail_groups_.end(); ++iter) {
2243 const DetailsGroup& group = iter->second;
2244 if (!group.container->visible())
2247 if (!ValidateGroup(group, VALIDATE_FINAL))
2254 void AutofillDialogViews::InputEditedOrActivated(ServerFieldType type,
2255 const gfx::Rect& bounds,
2257 DCHECK_NE(UNKNOWN_TYPE, type);
2259 ExpandingTextfield* textfield = TextfieldForType(type);
2260 views::Combobox* combobox = ComboboxForType(type);
2262 // Both views may be NULL if the event comes from an inactive section, which
2263 // may occur when using an IME.
2264 if (!combobox && !textfield)
2267 DCHECK_NE(!!combobox, !!textfield);
2268 DetailsGroup* group = textfield ? GroupForView(textfield) :
2269 GroupForView(combobox);
2270 base::string16 text = textfield ?
2271 textfield->GetText() :
2272 combobox->model()->GetItemAt(combobox->selected_index());
2275 delegate_->UserEditedOrActivatedInput(group->section,
2277 GetWidget()->GetNativeView(),
2282 // If the field is a textfield and is invalid, check if the text is now valid.
2283 // Many fields (i.e. CC#) are invalid for most of the duration of editing,
2284 // so flagging them as invalid prematurely is not helpful. However,
2285 // correcting a minor mistake (i.e. a wrong CC digit) should immediately
2286 // result in validation - positive user feedback.
2287 if (textfield && textfield->invalid() && was_edit) {
2288 SetValidityForInput(
2290 delegate_->InputValidityMessage(
2291 group->section, type, textfield->GetText()));
2293 // If the field transitioned from invalid to valid, re-validate the group,
2294 // since inter-field checks become meaningful with valid fields.
2295 if (!textfield->invalid())
2296 ValidateGroup(*group, VALIDATE_EDIT);
2299 if (delegate_->FieldControlsIcons(type))
2300 SetIconsForSection(group->section);
2302 SetEditabilityForSection(group->section);
2305 void AutofillDialogViews::UpdateButtonStripExtraView() {
2306 save_in_chrome_checkbox_container_->SetVisible(
2307 delegate_->ShouldOfferToSaveInChrome());
2309 gfx::Image image = delegate_->ButtonStripImage();
2310 button_strip_image_->SetVisible(!image.IsEmpty());
2311 button_strip_image_->SetImage(image.AsImageSkia());
2314 void AutofillDialogViews::ContentsPreferredSizeChanged() {
2315 if (updates_scope_ != 0) {
2316 needs_update_ = true;
2320 preferred_size_ = gfx::Size();
2322 if (GetWidget() && delegate_ && delegate_->GetWebContents()) {
2323 UpdateWebContentsModalDialogPosition(
2325 web_modal::WebContentsModalDialogManager::FromWebContents(
2326 delegate_->GetWebContents())->delegate()->
2327 GetWebContentsModalDialogHost());
2328 SetBoundsRect(bounds());
2332 AutofillDialogViews::DetailsGroup* AutofillDialogViews::GroupForSection(
2333 DialogSection section) {
2334 return &detail_groups_.find(section)->second;
2337 AutofillDialogViews::DetailsGroup* AutofillDialogViews::GroupForView(
2338 views::View* view) {
2341 views::View* input_view = GetAncestralInputView(view);
2345 for (DetailGroupMap::iterator iter = detail_groups_.begin();
2346 iter != detail_groups_.end(); ++iter) {
2347 DetailsGroup* group = &iter->second;
2348 if (input_view->parent() == group->manual_input)
2351 // Textfields need to check a second case, since they can be suggested
2352 // inputs instead of directly editable inputs. Those are accessed via
2353 // |suggested_info|.
2354 if (input_view == group->suggested_info->textfield()) {
2362 void AutofillDialogViews::EraseInvalidViewsInGroup(const DetailsGroup* group) {
2363 std::map<views::View*, base::string16>::iterator it = validity_map_.begin();
2364 while (it != validity_map_.end()) {
2365 if (GroupForView(it->first) == group)
2366 validity_map_.erase(it++);
2372 ExpandingTextfield* AutofillDialogViews::TextfieldForType(
2373 ServerFieldType type) {
2374 if (type == CREDIT_CARD_VERIFICATION_CODE) {
2375 DetailsGroup* group = GroupForSection(GetCreditCardSection());
2376 if (!group->manual_input->visible())
2377 return group->suggested_info->textfield();
2380 for (DetailGroupMap::iterator iter = detail_groups_.begin();
2381 iter != detail_groups_.end(); ++iter) {
2382 const DetailsGroup& group = iter->second;
2383 if (!delegate_->SectionIsActive(group.section))
2386 TextfieldMap::const_iterator text_mapping = group.textfields.find(type);
2387 if (text_mapping != group.textfields.end())
2388 return text_mapping->second;
2394 ServerFieldType AutofillDialogViews::TypeForTextfield(
2395 const views::View* textfield) {
2396 const views::View* expanding =
2397 textfield->GetAncestorWithClassName(ExpandingTextfield::kViewClassName);
2399 DetailsGroup* cc_group = GroupForSection(GetCreditCardSection());
2400 if (expanding == cc_group->suggested_info->textfield())
2401 return CREDIT_CARD_VERIFICATION_CODE;
2403 for (DetailGroupMap::const_iterator it = detail_groups_.begin();
2404 it != detail_groups_.end(); ++it) {
2405 if (!delegate_->SectionIsActive(it->second.section))
2408 for (TextfieldMap::const_iterator text_it = it->second.textfields.begin();
2409 text_it != it->second.textfields.end(); ++text_it) {
2410 if (expanding == text_it->second)
2411 return text_it->first;
2415 return UNKNOWN_TYPE;
2418 views::Combobox* AutofillDialogViews::ComboboxForType(
2419 ServerFieldType type) {
2420 for (DetailGroupMap::iterator iter = detail_groups_.begin();
2421 iter != detail_groups_.end(); ++iter) {
2422 const DetailsGroup& group = iter->second;
2423 if (!delegate_->SectionIsActive(group.section))
2426 ComboboxMap::const_iterator combo_mapping = group.comboboxes.find(type);
2427 if (combo_mapping != group.comboboxes.end())
2428 return combo_mapping->second;
2434 ServerFieldType AutofillDialogViews::TypeForCombobox(
2435 const views::Combobox* combobox) const {
2436 for (DetailGroupMap::const_iterator it = detail_groups_.begin();
2437 it != detail_groups_.end(); ++it) {
2438 const DetailsGroup& group = it->second;
2439 if (!delegate_->SectionIsActive(group.section))
2442 for (ComboboxMap::const_iterator combo_it = group.comboboxes.begin();
2443 combo_it != group.comboboxes.end(); ++combo_it) {
2444 if (combo_it->second == combobox)
2445 return combo_it->first;
2449 return UNKNOWN_TYPE;
2452 void AutofillDialogViews::DetailsContainerBoundsChanged() {
2454 error_bubble_->UpdatePosition();
2457 void AutofillDialogViews::SetIconsForSection(DialogSection section) {
2458 FieldValueMap user_input;
2459 GetUserInput(section, &user_input);
2460 FieldIconMap field_icons = delegate_->IconsForFields(user_input);
2461 TextfieldMap* textfields = &GroupForSection(section)->textfields;
2462 for (TextfieldMap::const_iterator textfield_it = textfields->begin();
2463 textfield_it != textfields->end();
2465 ServerFieldType field_type = textfield_it->first;
2466 FieldIconMap::const_iterator field_icon_it = field_icons.find(field_type);
2467 ExpandingTextfield* textfield = textfield_it->second;
2468 if (field_icon_it != field_icons.end())
2469 textfield->SetIcon(field_icon_it->second);
2471 textfield->SetTooltipIcon(delegate_->TooltipForField(field_type));
2475 void AutofillDialogViews::SetEditabilityForSection(DialogSection section) {
2476 const DetailInputs& inputs =
2477 delegate_->RequestedFieldsForSection(section);
2478 DetailsGroup* group = GroupForSection(section);
2480 for (DetailInputs::const_iterator iter = inputs.begin();
2481 iter != inputs.end(); ++iter) {
2482 const DetailInput& input = *iter;
2483 bool editable = delegate_->InputIsEditable(input, section);
2485 TextfieldMap::iterator text_mapping = group->textfields.find(input.type);
2486 if (text_mapping != group->textfields.end()) {
2487 ExpandingTextfield* textfield= text_mapping->second;
2488 textfield->SetEditable(editable);
2492 ComboboxMap::iterator combo_mapping = group->comboboxes.find(input.type);
2493 if (combo_mapping != group->comboboxes.end()) {
2494 views::Combobox* combobox = combo_mapping->second;
2495 combobox->SetEnabled(editable);
2500 void AutofillDialogViews::NonClientMousePressed() {
2501 delegate_->FocusMoved();
2504 AutofillDialogViews::DetailsGroup::DetailsGroup(DialogSection section)
2508 suggested_info(NULL),
2509 suggested_button(NULL) {}
2511 AutofillDialogViews::DetailsGroup::~DetailsGroup() {}
2513 } // namespace autofill