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