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