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