| // Copyright (c) 2012 The Chromium Authors. All rights reserved. |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| #include "chrome/browser/ui/views/autofill/autofill_dialog_views.h" |
| |
| #include <utility> |
| |
| #include "base/bind.h" |
| #include "base/location.h" |
| #include "base/strings/utf_string_conversions.h" |
| #include "chrome/browser/profiles/profile.h" |
| #include "chrome/browser/ui/autofill/autofill_dialog_sign_in_delegate.h" |
| #include "chrome/browser/ui/autofill/autofill_dialog_view_delegate.h" |
| #include "chrome/browser/ui/autofill/loading_animation.h" |
| #include "chrome/browser/ui/views/autofill/expanding_textfield.h" |
| #include "chrome/browser/ui/views/autofill/info_bubble.h" |
| #include "chrome/browser/ui/views/autofill/tooltip_icon.h" |
| #include "chrome/browser/ui/views/constrained_window_views.h" |
| #include "components/autofill/content/browser/wallet/wallet_service_url.h" |
| #include "components/autofill/core/browser/autofill_type.h" |
| #include "components/web_modal/web_contents_modal_dialog_host.h" |
| #include "components/web_modal/web_contents_modal_dialog_manager.h" |
| #include "components/web_modal/web_contents_modal_dialog_manager_delegate.h" |
| #include "content/public/browser/native_web_keyboard_event.h" |
| #include "content/public/browser/navigation_controller.h" |
| #include "content/public/browser/web_contents.h" |
| #include "grit/theme_resources.h" |
| #include "grit/ui_resources.h" |
| #include "third_party/skia/include/core/SkColor.h" |
| #include "ui/base/l10n/l10n_util.h" |
| #include "ui/base/models/combobox_model.h" |
| #include "ui/base/models/menu_model.h" |
| #include "ui/base/resource/resource_bundle.h" |
| #include "ui/events/event_handler.h" |
| #include "ui/gfx/animation/animation_delegate.h" |
| #include "ui/gfx/canvas.h" |
| #include "ui/gfx/color_utils.h" |
| #include "ui/gfx/font_list.h" |
| #include "ui/gfx/path.h" |
| #include "ui/gfx/point.h" |
| #include "ui/gfx/skia_util.h" |
| #include "ui/views/background.h" |
| #include "ui/views/border.h" |
| #include "ui/views/bubble/bubble_border.h" |
| #include "ui/views/bubble/bubble_frame_view.h" |
| #include "ui/views/controls/button/blue_button.h" |
| #include "ui/views/controls/button/checkbox.h" |
| #include "ui/views/controls/button/label_button.h" |
| #include "ui/views/controls/button/label_button_border.h" |
| #include "ui/views/controls/button/menu_button.h" |
| #include "ui/views/controls/combobox/combobox.h" |
| #include "ui/views/controls/image_view.h" |
| #include "ui/views/controls/label.h" |
| #include "ui/views/controls/link.h" |
| #include "ui/views/controls/menu/menu_runner.h" |
| #include "ui/views/controls/separator.h" |
| #include "ui/views/controls/styled_label.h" |
| #include "ui/views/controls/textfield/textfield.h" |
| #include "ui/views/controls/webview/webview.h" |
| #include "ui/views/layout/box_layout.h" |
| #include "ui/views/layout/fill_layout.h" |
| #include "ui/views/layout/grid_layout.h" |
| #include "ui/views/layout/layout_constants.h" |
| #include "ui/views/painter.h" |
| #include "ui/views/widget/widget.h" |
| #include "ui/views/window/dialog_client_view.h" |
| #include "ui/views/window/non_client_view.h" |
| |
| namespace autofill { |
| |
| namespace { |
| |
| // The width for the section container. |
| const int kSectionContainerWidth = 440; |
| |
| // The minimum useful height of the contents area of the dialog. |
| const int kMinimumContentsHeight = 101; |
| |
| // The default height of the loading shield, also its minimum size. |
| const int kInitialLoadingShieldHeight = 150; |
| |
| // Horizontal padding between text and other elements (in pixels). |
| const int kAroundTextPadding = 4; |
| |
| // The space between the edges of a notification bar and the text within (in |
| // pixels). |
| const int kNotificationPadding = 17; |
| |
| // Vertical padding above and below each detail section (in pixels). |
| const int kDetailSectionVerticalPadding = 10; |
| |
| const int kArrowHeight = 7; |
| const int kArrowWidth = 2 * kArrowHeight; |
| |
| // The padding inside the edges of the dialog, in pixels. |
| const int kDialogEdgePadding = 20; |
| |
| // The vertical padding between rows of manual inputs (in pixels). |
| const int kManualInputRowPadding = 10; |
| |
| // Slight shading for mouse hover and legal document background. |
| SkColor kShadingColor = SkColorSetARGB(7, 0, 0, 0); |
| |
| // A border color for the legal document view. |
| SkColor kSubtleBorderColor = SkColorSetARGB(10, 0, 0, 0); |
| |
| // The top and bottom padding, in pixels, for the suggestions menu dropdown |
| // arrows. |
| const int kMenuButtonTopInset = 3; |
| const int kMenuButtonBottomInset = 6; |
| |
| // The height in pixels of the padding above and below the overlay message view. |
| const int kOverlayMessageVerticalPadding = 34; |
| |
| // Spacing below image and above text messages in overlay view. |
| const int kOverlayImageBottomMargin = 100; |
| |
| const char kNotificationAreaClassName[] = "autofill/NotificationArea"; |
| const char kOverlayViewClassName[] = "autofill/OverlayView"; |
| const char kSectionContainerClassName[] = "autofill/SectionContainer"; |
| const char kSuggestedButtonClassName[] = "autofill/SuggestedButton"; |
| |
| // Draws an arrow at the top of |canvas| pointing to |tip_x|. |
| void DrawArrow(gfx::Canvas* canvas, |
| int tip_x, |
| const SkColor& fill_color, |
| const SkColor& stroke_color) { |
| const int arrow_half_width = kArrowWidth / 2.0f; |
| |
| SkPath arrow; |
| arrow.moveTo(tip_x - arrow_half_width, kArrowHeight); |
| arrow.lineTo(tip_x, 0); |
| arrow.lineTo(tip_x + arrow_half_width, kArrowHeight); |
| |
| SkPaint fill_paint; |
| fill_paint.setColor(fill_color); |
| canvas->DrawPath(arrow, fill_paint); |
| |
| if (stroke_color != SK_ColorTRANSPARENT) { |
| SkPaint stroke_paint; |
| stroke_paint.setColor(stroke_color); |
| stroke_paint.setStyle(SkPaint::kStroke_Style); |
| canvas->DrawPath(arrow, stroke_paint); |
| } |
| } |
| |
| void SelectComboboxValueOrSetToDefault(views::Combobox* combobox, |
| const base::string16& value) { |
| if (!combobox->SelectValue(value)) |
| combobox->SetSelectedIndex(combobox->model()->GetDefaultIndex()); |
| } |
| |
| // This class handles layout for the first row of a SuggestionView. |
| // It exists to circumvent shortcomings of GridLayout and BoxLayout (namely that |
| // the former doesn't fully respect child visibility, and that the latter won't |
| // expand a single child). |
| class SectionRowView : public views::View { |
| public: |
| SectionRowView() { SetBorder(views::Border::CreateEmptyBorder(10, 0, 0, 0)); } |
| |
| virtual ~SectionRowView() {} |
| |
| // views::View implementation: |
| virtual gfx::Size GetPreferredSize() const OVERRIDE { |
| int height = 0; |
| int width = 0; |
| for (int i = 0; i < child_count(); ++i) { |
| if (child_at(i)->visible()) { |
| if (width > 0) |
| width += kAroundTextPadding; |
| |
| gfx::Size size = child_at(i)->GetPreferredSize(); |
| height = std::max(height, size.height()); |
| width += size.width(); |
| } |
| } |
| |
| gfx::Insets insets = GetInsets(); |
| return gfx::Size(width + insets.width(), height + insets.height()); |
| } |
| |
| virtual void Layout() OVERRIDE { |
| const gfx::Rect bounds = GetContentsBounds(); |
| |
| // Icon is left aligned. |
| int start_x = bounds.x(); |
| views::View* icon = child_at(0); |
| if (icon->visible()) { |
| icon->SizeToPreferredSize(); |
| icon->SetX(start_x); |
| icon->SetY(bounds.y() + |
| (bounds.height() - icon->bounds().height()) / 2); |
| start_x += icon->bounds().width() + kAroundTextPadding; |
| } |
| |
| // Textfield is right aligned. |
| int end_x = bounds.width(); |
| views::View* textfield = child_at(2); |
| if (textfield->visible()) { |
| const int preferred_width = textfield->GetPreferredSize().width(); |
| textfield->SetBounds(bounds.width() - preferred_width, bounds.y(), |
| preferred_width, bounds.height()); |
| end_x = textfield->bounds().x() - kAroundTextPadding; |
| } |
| |
| // Label takes up all the space in between. |
| views::View* label = child_at(1); |
| if (label->visible()) |
| label->SetBounds(start_x, bounds.y(), end_x - start_x, bounds.height()); |
| |
| views::View::Layout(); |
| } |
| |
| private: |
| DISALLOW_COPY_AND_ASSIGN(SectionRowView); |
| }; |
| |
| // A view that propagates visibility and preferred size changes. |
| class LayoutPropagationView : public views::View { |
| public: |
| LayoutPropagationView() {} |
| virtual ~LayoutPropagationView() {} |
| |
| protected: |
| virtual void ChildVisibilityChanged(views::View* child) OVERRIDE { |
| PreferredSizeChanged(); |
| } |
| virtual void ChildPreferredSizeChanged(views::View* child) OVERRIDE { |
| PreferredSizeChanged(); |
| } |
| |
| private: |
| DISALLOW_COPY_AND_ASSIGN(LayoutPropagationView); |
| }; |
| |
| // A View for a single notification banner. |
| class NotificationView : public views::View, |
| public views::ButtonListener, |
| public views::StyledLabelListener { |
| public: |
| NotificationView(const DialogNotification& data, |
| AutofillDialogViewDelegate* delegate) |
| : data_(data), |
| delegate_(delegate), |
| checkbox_(NULL) { |
| scoped_ptr<views::View> label_view; |
| if (data.HasCheckbox()) { |
| scoped_ptr<views::Checkbox> checkbox( |
| new views::Checkbox(base::string16())); |
| checkbox->SetText(data.display_text()); |
| checkbox->SetTextMultiLine(true); |
| checkbox->SetHorizontalAlignment(gfx::ALIGN_LEFT); |
| checkbox->SetTextColor(views::Button::STATE_NORMAL, |
| data.GetTextColor()); |
| checkbox->SetTextColor(views::Button::STATE_HOVERED, |
| data.GetTextColor()); |
| checkbox->SetChecked(data.checked()); |
| checkbox->set_listener(this); |
| checkbox_ = checkbox.get(); |
| label_view.reset(checkbox.release()); |
| } else { |
| scoped_ptr<views::StyledLabel> label(new views::StyledLabel( |
| data.display_text(), this)); |
| label->set_auto_color_readability_enabled(false); |
| |
| views::StyledLabel::RangeStyleInfo text_style; |
| text_style.color = data.GetTextColor(); |
| |
| if (data.link_range().is_empty()) { |
| label->AddStyleRange(gfx::Range(0, data.display_text().size()), |
| text_style); |
| } else { |
| gfx::Range prefix_range(0, data.link_range().start()); |
| if (!prefix_range.is_empty()) |
| label->AddStyleRange(prefix_range, text_style); |
| |
| label->AddStyleRange( |
| data.link_range(), |
| views::StyledLabel::RangeStyleInfo::CreateForLink()); |
| |
| gfx::Range suffix_range(data.link_range().end(), |
| data.display_text().size()); |
| if (!suffix_range.is_empty()) |
| label->AddStyleRange(suffix_range, text_style); |
| } |
| |
| label_view.reset(label.release()); |
| } |
| |
| AddChildView(label_view.release()); |
| |
| if (!data.tooltip_text().empty()) |
| AddChildView(new TooltipIcon(data.tooltip_text())); |
| |
| set_background( |
| views::Background::CreateSolidBackground(data.GetBackgroundColor())); |
| SetBorder(views::Border::CreateSolidSidedBorder( |
| 1, 0, 1, 0, data.GetBorderColor())); |
| } |
| |
| virtual ~NotificationView() {} |
| |
| views::Checkbox* checkbox() { |
| return checkbox_; |
| } |
| |
| // views::View implementation. |
| virtual gfx::Insets GetInsets() const OVERRIDE { |
| int vertical_padding = kNotificationPadding; |
| if (checkbox_) |
| vertical_padding -= 3; |
| return gfx::Insets(vertical_padding, kDialogEdgePadding, |
| vertical_padding, kDialogEdgePadding); |
| } |
| |
| virtual int GetHeightForWidth(int width) const OVERRIDE { |
| int label_width = width - GetInsets().width(); |
| if (child_count() > 1) { |
| const views::View* tooltip_icon = child_at(1); |
| label_width -= tooltip_icon->GetPreferredSize().width() + |
| kDialogEdgePadding; |
| } |
| |
| return child_at(0)->GetHeightForWidth(label_width) + GetInsets().height(); |
| } |
| |
| virtual void Layout() OVERRIDE { |
| // Surprisingly, GetContentsBounds() doesn't consult GetInsets(). |
| gfx::Rect bounds = GetLocalBounds(); |
| bounds.Inset(GetInsets()); |
| int right_bound = bounds.right(); |
| |
| if (child_count() > 1) { |
| // The icon takes up the entire vertical space and an extra 20px on |
| // each side. This increases the hover target for the tooltip. |
| views::View* tooltip_icon = child_at(1); |
| gfx::Size icon_size = tooltip_icon->GetPreferredSize(); |
| int icon_width = icon_size.width() + kDialogEdgePadding; |
| right_bound -= icon_width; |
| tooltip_icon->SetBounds( |
| right_bound, 0, |
| icon_width + kDialogEdgePadding, GetLocalBounds().height()); |
| } |
| |
| child_at(0)->SetBounds(bounds.x(), bounds.y(), |
| right_bound - bounds.x(), bounds.height()); |
| } |
| |
| // views::ButtonListener implementation. |
| virtual void ButtonPressed(views::Button* sender, |
| const ui::Event& event) OVERRIDE { |
| DCHECK_EQ(sender, checkbox_); |
| delegate_->NotificationCheckboxStateChanged(data_.type(), |
| checkbox_->checked()); |
| } |
| |
| // views::StyledLabelListener implementation. |
| virtual void StyledLabelLinkClicked(const gfx::Range& range, int event_flags) |
| OVERRIDE { |
| delegate_->LinkClicked(data_.link_url()); |
| } |
| |
| private: |
| // The model data for this notification. |
| DialogNotification data_; |
| |
| // The delegate that handles interaction with |this|. |
| AutofillDialogViewDelegate* delegate_; |
| |
| // The checkbox associated with this notification, or NULL if there is none. |
| views::Checkbox* checkbox_; |
| |
| DISALLOW_COPY_AND_ASSIGN(NotificationView); |
| }; |
| |
| // A view that displays a loading message with some dancing dots. |
| class LoadingAnimationView : public views::View, |
| public gfx::AnimationDelegate { |
| public: |
| explicit LoadingAnimationView(const base::string16& text) : |
| container_(new views::View()) { |
| AddChildView(container_); |
| container_->SetLayoutManager( |
| new views::BoxLayout(views::BoxLayout::kHorizontal, 0, 0, 0)); |
| |
| const gfx::FontList& font_list = |
| ui::ResourceBundle::GetSharedInstance().GetFontList( |
| ui::ResourceBundle::LargeFont); |
| animation_.reset(new LoadingAnimation(this, font_list.GetHeight())); |
| |
| container_->AddChildView(new views::Label(text, font_list)); |
| |
| for (size_t i = 0; i < 3; ++i) { |
| container_->AddChildView( |
| new views::Label(base::ASCIIToUTF16("."), font_list)); |
| } |
| } |
| |
| virtual ~LoadingAnimationView() {} |
| |
| // views::View implementation. |
| virtual void SetVisible(bool visible) OVERRIDE { |
| if (visible) |
| animation_->Start(); |
| else |
| animation_->Reset(); |
| |
| views::View::SetVisible(visible); |
| } |
| |
| virtual void Layout() OVERRIDE { |
| gfx::Size container_size = container_->GetPreferredSize(); |
| gfx::Rect container_bounds((width() - container_size.width()) / 2, |
| (height() - container_size.height()) / 2, |
| container_size.width(), |
| container_size.height()); |
| container_->SetBoundsRect(container_bounds); |
| container_->Layout(); |
| |
| for (size_t i = 0; i < 3; ++i) { |
| views::View* dot = container_->child_at(i + 1); |
| dot->SetY(dot->y() + animation_->GetCurrentValueForDot(i)); |
| } |
| } |
| |
| virtual void OnNativeThemeChanged(const ui::NativeTheme* theme) OVERRIDE { |
| set_background(views::Background::CreateSolidBackground( |
| theme->GetSystemColor(ui::NativeTheme::kColorId_DialogBackground))); |
| } |
| |
| // gfx::AnimationDelegate implementation. |
| virtual void AnimationProgressed(const gfx::Animation* animation) OVERRIDE { |
| DCHECK_EQ(animation, animation_.get()); |
| Layout(); |
| } |
| |
| private: |
| // Contains the "Loading" label and the dots. |
| views::View* container_; |
| |
| scoped_ptr<LoadingAnimation> animation_; |
| |
| DISALLOW_COPY_AND_ASSIGN(LoadingAnimationView); |
| }; |
| |
| // Gets either the Combobox or ExpandingTextfield that is an ancestor (including |
| // self) of |view|. |
| views::View* GetAncestralInputView(views::View* view) { |
| if (view->GetClassName() == views::Combobox::kViewClassName) |
| return view; |
| |
| return view->GetAncestorWithClassName(ExpandingTextfield::kViewClassName); |
| } |
| |
| // A class that informs |delegate_| when an unhandled mouse press occurs. |
| class MousePressedHandler : public ui::EventHandler { |
| public: |
| explicit MousePressedHandler(AutofillDialogViewDelegate* delegate) |
| : delegate_(delegate) {} |
| |
| // ui::EventHandler implementation. |
| virtual void OnMouseEvent(ui::MouseEvent* event) OVERRIDE { |
| if (event->type() == ui::ET_MOUSE_PRESSED && !event->handled()) |
| delegate_->FocusMoved(); |
| } |
| |
| private: |
| AutofillDialogViewDelegate* const delegate_; |
| |
| DISALLOW_COPY_AND_ASSIGN(MousePressedHandler); |
| }; |
| |
| } // namespace |
| |
| // AutofillDialogViews::AccountChooser ----------------------------------------- |
| |
| AutofillDialogViews::AccountChooser::AccountChooser( |
| AutofillDialogViewDelegate* delegate) |
| : image_(new views::ImageView()), |
| menu_button_(new views::MenuButton(NULL, base::string16(), this, true)), |
| link_(new views::Link()), |
| delegate_(delegate) { |
| SetBorder(views::Border::CreateEmptyBorder(0, 0, 0, 10)); |
| SetLayoutManager( |
| new views::BoxLayout(views::BoxLayout::kHorizontal, 0, 0, |
| kAroundTextPadding)); |
| AddChildView(image_); |
| |
| menu_button_->set_background(NULL); |
| menu_button_->SetBorder(views::Border::NullBorder()); |
| gfx::Insets insets = GetInsets(); |
| menu_button_->SetFocusPainter( |
| views::Painter::CreateDashedFocusPainterWithInsets(insets)); |
| menu_button_->SetFocusable(true); |
| AddChildView(menu_button_); |
| |
| link_->set_listener(this); |
| AddChildView(link_); |
| } |
| |
| AutofillDialogViews::AccountChooser::~AccountChooser() {} |
| |
| void AutofillDialogViews::AccountChooser::Update() { |
| SetVisible(delegate_->ShouldShowAccountChooser()); |
| |
| gfx::Image icon = delegate_->AccountChooserImage(); |
| image_->SetImage(icon.AsImageSkia()); |
| menu_button_->SetText(delegate_->AccountChooserText()); |
| menu_button_->set_min_size(gfx::Size()); |
| |
| bool show_link = !delegate_->MenuModelForAccountChooser(); |
| menu_button_->SetVisible(!show_link); |
| link_->SetText(delegate_->SignInLinkText()); |
| link_->SetVisible(show_link); |
| |
| menu_runner_.reset(); |
| |
| PreferredSizeChanged(); |
| } |
| |
| void AutofillDialogViews::AccountChooser::OnMenuButtonClicked( |
| views::View* source, |
| const gfx::Point& point) { |
| DCHECK_EQ(menu_button_, source); |
| |
| ui::MenuModel* model = delegate_->MenuModelForAccountChooser(); |
| if (!model) |
| return; |
| |
| menu_runner_.reset(new views::MenuRunner(model, 0)); |
| if (menu_runner_->RunMenuAt(source->GetWidget(), |
| NULL, |
| source->GetBoundsInScreen(), |
| views::MENU_ANCHOR_TOPRIGHT, |
| ui::MENU_SOURCE_NONE) == |
| views::MenuRunner::MENU_DELETED) { |
| return; |
| } |
| } |
| |
| views::View* AutofillDialogViews::GetLoadingShieldForTesting() { |
| return loading_shield_; |
| } |
| |
| views::WebView* AutofillDialogViews::GetSignInWebViewForTesting() { |
| return sign_in_web_view_; |
| } |
| |
| views::View* AutofillDialogViews::GetNotificationAreaForTesting() { |
| return notification_area_; |
| } |
| |
| views::View* AutofillDialogViews::GetScrollableAreaForTesting() { |
| return scrollable_area_; |
| } |
| |
| void AutofillDialogViews::AccountChooser::LinkClicked(views::Link* source, |
| int event_flags) { |
| delegate_->SignInLinkClicked(); |
| } |
| |
| // AutofillDialogViews::OverlayView -------------------------------------------- |
| |
| AutofillDialogViews::OverlayView::OverlayView( |
| AutofillDialogViewDelegate* delegate) |
| : delegate_(delegate), |
| image_view_(new views::ImageView()), |
| message_view_(new views::Label()) { |
| message_view_->SetAutoColorReadabilityEnabled(false); |
| message_view_->SetMultiLine(true); |
| |
| AddChildView(image_view_); |
| AddChildView(message_view_); |
| } |
| |
| AutofillDialogViews::OverlayView::~OverlayView() {} |
| |
| int AutofillDialogViews::OverlayView::GetHeightForContentsForWidth(int width) { |
| // In this case, 0 means "no preference". |
| if (!message_view_->visible()) |
| return 0; |
| |
| return kOverlayImageBottomMargin + |
| views::kButtonVEdgeMarginNew + |
| message_view_->GetHeightForWidth(width) + |
| image_view_->GetHeightForWidth(width); |
| } |
| |
| void AutofillDialogViews::OverlayView::UpdateState() { |
| const DialogOverlayState& state = delegate_->GetDialogOverlay(); |
| |
| if (state.image.IsEmpty()) { |
| SetVisible(false); |
| return; |
| } |
| |
| image_view_->SetImage(state.image.ToImageSkia()); |
| |
| message_view_->SetVisible(!state.string.text.empty()); |
| message_view_->SetText(state.string.text); |
| message_view_->SetFontList(state.string.font_list); |
| message_view_->SetEnabledColor(GetNativeTheme()->GetSystemColor( |
| ui::NativeTheme::kColorId_TextfieldReadOnlyColor)); |
| |
| message_view_->SetBorder( |
| views::Border::CreateEmptyBorder(kOverlayMessageVerticalPadding, |
| kDialogEdgePadding, |
| kOverlayMessageVerticalPadding, |
| kDialogEdgePadding)); |
| |
| SetVisible(true); |
| } |
| |
| gfx::Insets AutofillDialogViews::OverlayView::GetInsets() const { |
| return gfx::Insets(12, 12, 12, 12); |
| } |
| |
| void AutofillDialogViews::OverlayView::Layout() { |
| gfx::Rect bounds = ContentBoundsSansBubbleBorder(); |
| if (!message_view_->visible()) { |
| image_view_->SetBoundsRect(bounds); |
| return; |
| } |
| |
| int message_height = message_view_->GetHeightForWidth(bounds.width()); |
| int y = bounds.bottom() - message_height; |
| message_view_->SetBounds(bounds.x(), y, bounds.width(), message_height); |
| |
| gfx::Size image_size = image_view_->GetPreferredSize(); |
| y -= image_size.height() + kOverlayImageBottomMargin; |
| image_view_->SetBounds(bounds.x(), y, bounds.width(), image_size.height()); |
| } |
| |
| const char* AutofillDialogViews::OverlayView::GetClassName() const { |
| return kOverlayViewClassName; |
| } |
| |
| void AutofillDialogViews::OverlayView::OnPaint(gfx::Canvas* canvas) { |
| // BubbleFrameView doesn't mask the window, it just draws the border via |
| // image assets. Match that rounding here. |
| gfx::Rect rect = ContentBoundsSansBubbleBorder(); |
| const SkScalar kCornerRadius = SkIntToScalar( |
| GetBubbleBorder() ? GetBubbleBorder()->GetBorderCornerRadius() : 2); |
| gfx::Path window_mask; |
| window_mask.addRoundRect(gfx::RectToSkRect(rect), |
| kCornerRadius, kCornerRadius); |
| canvas->ClipPath(window_mask, false); |
| |
| OnPaintBackground(canvas); |
| |
| // Draw the arrow, border, and fill for the bottom area. |
| if (message_view_->visible()) { |
| const int arrow_half_width = kArrowWidth / 2.0f; |
| SkPath arrow; |
| int y = message_view_->y() - 1; |
| // Note that we purposely draw slightly outside of |rect| so that the |
| // stroke is hidden on the sides. |
| arrow.moveTo(rect.x() - 1, y); |
| arrow.rLineTo(rect.width() / 2 - arrow_half_width, 0); |
| arrow.rLineTo(arrow_half_width, -kArrowHeight); |
| arrow.rLineTo(arrow_half_width, kArrowHeight); |
| arrow.lineTo(rect.right() + 1, y); |
| arrow.lineTo(rect.right() + 1, rect.bottom() + 1); |
| arrow.lineTo(rect.x() - 1, rect.bottom() + 1); |
| arrow.close(); |
| |
| // The mocked alpha blends were 7 for background & 10 for the border against |
| // a very bright background. The eye perceives luminance differences of |
| // darker colors much less than lighter colors, so increase the alpha blend |
| // amount the darker the color (lower the luminance). |
| SkPaint paint; |
| SkColor background_color = background()->get_color(); |
| int background_luminance = |
| color_utils::GetLuminanceForColor(background_color); |
| int background_alpha = static_cast<int>( |
| 7 + 15 * (255 - background_luminance) / 255); |
| int subtle_border_alpha = static_cast<int>( |
| 10 + 20 * (255 - background_luminance) / 255); |
| |
| paint.setColor(color_utils::BlendTowardOppositeLuminance( |
| background_color, background_alpha)); |
| paint.setStyle(SkPaint::kFill_Style); |
| canvas->DrawPath(arrow, paint); |
| paint.setColor(color_utils::BlendTowardOppositeLuminance( |
| background_color, subtle_border_alpha)); |
| paint.setStyle(SkPaint::kStroke_Style); |
| canvas->DrawPath(arrow, paint); |
| } |
| |
| PaintChildren(canvas, views::CullSet()); |
| } |
| |
| void AutofillDialogViews::OverlayView::OnNativeThemeChanged( |
| const ui::NativeTheme* theme) { |
| set_background(views::Background::CreateSolidBackground( |
| theme->GetSystemColor(ui::NativeTheme::kColorId_DialogBackground))); |
| } |
| |
| views::BubbleBorder* AutofillDialogViews::OverlayView::GetBubbleBorder() { |
| views::View* frame = GetWidget()->non_client_view()->frame_view(); |
| std::string bubble_frame_view_name(views::BubbleFrameView::kViewClassName); |
| if (frame->GetClassName() == bubble_frame_view_name) |
| return static_cast<views::BubbleFrameView*>(frame)->bubble_border(); |
| NOTREACHED(); |
| return NULL; |
| } |
| |
| gfx::Rect AutofillDialogViews::OverlayView::ContentBoundsSansBubbleBorder() { |
| gfx::Rect bounds = GetContentsBounds(); |
| int bubble_width = 5; |
| if (GetBubbleBorder()) |
| bubble_width = GetBubbleBorder()->GetBorderThickness(); |
| bounds.Inset(bubble_width, bubble_width, bubble_width, bubble_width); |
| return bounds; |
| } |
| |
| // AutofillDialogViews::NotificationArea --------------------------------------- |
| |
| AutofillDialogViews::NotificationArea::NotificationArea( |
| AutofillDialogViewDelegate* delegate) |
| : delegate_(delegate) { |
| // Reserve vertical space for the arrow (regardless of whether one exists). |
| // The -1 accounts for the border. |
| SetBorder(views::Border::CreateEmptyBorder(kArrowHeight - 1, 0, 0, 0)); |
| |
| views::BoxLayout* box_layout = |
| new views::BoxLayout(views::BoxLayout::kVertical, 0, 0, 0); |
| SetLayoutManager(box_layout); |
| } |
| |
| AutofillDialogViews::NotificationArea::~NotificationArea() {} |
| |
| void AutofillDialogViews::NotificationArea::SetNotifications( |
| const std::vector<DialogNotification>& notifications) { |
| notifications_ = notifications; |
| |
| RemoveAllChildViews(true); |
| |
| if (notifications_.empty()) |
| return; |
| |
| for (size_t i = 0; i < notifications_.size(); ++i) { |
| const DialogNotification& notification = notifications_[i]; |
| scoped_ptr<NotificationView> view(new NotificationView(notification, |
| delegate_)); |
| |
| AddChildView(view.release()); |
| } |
| |
| PreferredSizeChanged(); |
| } |
| |
| gfx::Size AutofillDialogViews::NotificationArea::GetPreferredSize() const { |
| gfx::Size size = views::View::GetPreferredSize(); |
| // Ensure that long notifications wrap and don't enlarge the dialog. |
| size.set_width(1); |
| return size; |
| } |
| |
| const char* AutofillDialogViews::NotificationArea::GetClassName() const { |
| return kNotificationAreaClassName; |
| } |
| |
| void AutofillDialogViews::NotificationArea::PaintChildren( |
| gfx::Canvas* canvas, |
| const views::CullSet& cull_set) { |
| } |
| |
| void AutofillDialogViews::NotificationArea::OnPaint(gfx::Canvas* canvas) { |
| views::View::OnPaint(canvas); |
| views::View::PaintChildren(canvas, views::CullSet()); |
| |
| if (HasArrow()) { |
| DrawArrow( |
| canvas, |
| GetMirroredXInView(width() - arrow_centering_anchor_->width() / 2.0f), |
| notifications_[0].GetBackgroundColor(), |
| notifications_[0].GetBorderColor()); |
| } |
| } |
| |
| void AutofillDialogViews::OnWidgetDestroying(views::Widget* widget) { |
| if (widget == window_) |
| window_->GetRootView()->RemovePostTargetHandler(event_handler_.get()); |
| } |
| |
| void AutofillDialogViews::OnWidgetClosing(views::Widget* widget) { |
| observer_.Remove(widget); |
| if (error_bubble_ && error_bubble_->GetWidget() == widget) |
| error_bubble_ = NULL; |
| } |
| |
| void AutofillDialogViews::OnWidgetBoundsChanged(views::Widget* widget, |
| const gfx::Rect& new_bounds) { |
| if (error_bubble_ && error_bubble_->GetWidget() == widget) |
| return; |
| |
| // Notify the web contents of its new auto-resize limits. |
| if (sign_in_delegate_ && sign_in_web_view_->visible()) { |
| sign_in_delegate_->UpdateLimitsAndEnableAutoResize( |
| GetMinimumSignInViewSize(), GetMaximumSignInViewSize()); |
| } |
| HideErrorBubble(); |
| } |
| |
| bool AutofillDialogViews::NotificationArea::HasArrow() { |
| return !notifications_.empty() && notifications_[0].HasArrow() && |
| arrow_centering_anchor_.get(); |
| } |
| |
| // AutofillDialogViews::SectionContainer --------------------------------------- |
| |
| AutofillDialogViews::SectionContainer::SectionContainer( |
| const base::string16& label, |
| views::View* controls, |
| views::Button* proxy_button) |
| : proxy_button_(proxy_button), |
| forward_mouse_events_(false) { |
| set_notify_enter_exit_on_child(true); |
| |
| SetBorder(views::Border::CreateEmptyBorder(kDetailSectionVerticalPadding, |
| kDialogEdgePadding, |
| kDetailSectionVerticalPadding, |
| kDialogEdgePadding)); |
| |
| ui::ResourceBundle& rb = ui::ResourceBundle::GetSharedInstance(); |
| views::Label* label_view = new views::Label( |
| label, rb.GetFontList(ui::ResourceBundle::BoldFont)); |
| label_view->SetHorizontalAlignment(gfx::ALIGN_LEFT); |
| |
| views::View* label_bar = new views::View(); |
| views::GridLayout* label_bar_layout = new views::GridLayout(label_bar); |
| label_bar->SetLayoutManager(label_bar_layout); |
| const int kColumnSetId = 0; |
| views::ColumnSet* columns = label_bar_layout->AddColumnSet(kColumnSetId); |
| columns->AddColumn( |
| views::GridLayout::LEADING, |
| views::GridLayout::LEADING, |
| 0, |
| views::GridLayout::FIXED, |
| kSectionContainerWidth - proxy_button->GetPreferredSize().width(), |
| 0); |
| columns->AddColumn(views::GridLayout::LEADING, |
| views::GridLayout::LEADING, |
| 0, |
| views::GridLayout::USE_PREF, |
| 0, |
| 0); |
| label_bar_layout->StartRow(0, kColumnSetId); |
| label_bar_layout->AddView(label_view); |
| label_bar_layout->AddView(proxy_button); |
| |
| SetLayoutManager(new views::BoxLayout(views::BoxLayout::kVertical, 0, 0, 0)); |
| AddChildView(label_bar); |
| AddChildView(controls); |
| } |
| |
| AutofillDialogViews::SectionContainer::~SectionContainer() {} |
| |
| void AutofillDialogViews::SectionContainer::SetActive(bool active) { |
| bool is_active = active && proxy_button_->visible(); |
| if (is_active == !!background()) |
| return; |
| |
| set_background(is_active ? |
| views::Background::CreateSolidBackground(kShadingColor) : |
| NULL); |
| SchedulePaint(); |
| } |
| |
| void AutofillDialogViews::SectionContainer::SetForwardMouseEvents( |
| bool forward) { |
| forward_mouse_events_ = forward; |
| if (!forward) |
| set_background(NULL); |
| } |
| |
| const char* AutofillDialogViews::SectionContainer::GetClassName() const { |
| return kSectionContainerClassName; |
| } |
| |
| void AutofillDialogViews::SectionContainer::OnMouseMoved( |
| const ui::MouseEvent& event) { |
| SetActive(ShouldForwardEvent(event)); |
| } |
| |
| void AutofillDialogViews::SectionContainer::OnMouseEntered( |
| const ui::MouseEvent& event) { |
| if (!ShouldForwardEvent(event)) |
| return; |
| |
| SetActive(true); |
| proxy_button_->OnMouseEntered(ProxyEvent(event)); |
| SchedulePaint(); |
| } |
| |
| void AutofillDialogViews::SectionContainer::OnMouseExited( |
| const ui::MouseEvent& event) { |
| SetActive(false); |
| if (!ShouldForwardEvent(event)) |
| return; |
| |
| proxy_button_->OnMouseExited(ProxyEvent(event)); |
| SchedulePaint(); |
| } |
| |
| bool AutofillDialogViews::SectionContainer::OnMousePressed( |
| const ui::MouseEvent& event) { |
| if (!ShouldForwardEvent(event)) |
| return false; |
| |
| return proxy_button_->OnMousePressed(ProxyEvent(event)); |
| } |
| |
| void AutofillDialogViews::SectionContainer::OnMouseReleased( |
| const ui::MouseEvent& event) { |
| if (!ShouldForwardEvent(event)) |
| return; |
| |
| proxy_button_->OnMouseReleased(ProxyEvent(event)); |
| } |
| |
| void AutofillDialogViews::SectionContainer::OnGestureEvent( |
| ui::GestureEvent* event) { |
| if (!ShouldForwardEvent(*event)) |
| return; |
| |
| proxy_button_->OnGestureEvent(event); |
| } |
| |
| views::View* AutofillDialogViews::SectionContainer::GetEventHandlerForRect( |
| const gfx::Rect& rect) { |
| // TODO(tdanderson): Modify this function to support rect-based event |
| // targeting. |
| |
| views::View* handler = views::View::GetEventHandlerForRect(rect); |
| // If the event is not in the label bar and there's no background to be |
| // cleared, let normal event handling take place. |
| if (!background() && |
| rect.CenterPoint().y() > child_at(0)->bounds().bottom()) { |
| return handler; |
| } |
| |
| // Special case for (CVC) inputs in the suggestion view. |
| if (forward_mouse_events_ && |
| handler->GetAncestorWithClassName(ExpandingTextfield::kViewClassName)) { |
| return handler; |
| } |
| |
| // Special case for the proxy button itself. |
| if (handler == proxy_button_) |
| return handler; |
| |
| return this; |
| } |
| |
| // static |
| ui::MouseEvent AutofillDialogViews::SectionContainer::ProxyEvent( |
| const ui::MouseEvent& event) { |
| ui::MouseEvent event_copy = event; |
| event_copy.set_location(gfx::Point()); |
| return event_copy; |
| } |
| |
| bool AutofillDialogViews::SectionContainer::ShouldForwardEvent( |
| const ui::LocatedEvent& event) { |
| // Always forward events on the label bar. |
| return forward_mouse_events_ || event.y() <= child_at(0)->bounds().bottom(); |
| } |
| |
| // AutofillDialogViews::SuggestedButton ---------------------------------------- |
| |
| AutofillDialogViews::SuggestedButton::SuggestedButton( |
| views::MenuButtonListener* listener) |
| : views::MenuButton(NULL, base::string16(), listener, false) { |
| const int kFocusBorderWidth = 1; |
| SetBorder(views::Border::CreateEmptyBorder(kMenuButtonTopInset, |
| kFocusBorderWidth, |
| kMenuButtonBottomInset, |
| kFocusBorderWidth)); |
| gfx::Insets insets = GetInsets(); |
| insets += gfx::Insets(-kFocusBorderWidth, -kFocusBorderWidth, |
| -kFocusBorderWidth, -kFocusBorderWidth); |
| SetFocusPainter( |
| views::Painter::CreateDashedFocusPainterWithInsets(insets)); |
| SetFocusable(true); |
| } |
| |
| AutofillDialogViews::SuggestedButton::~SuggestedButton() {} |
| |
| gfx::Size AutofillDialogViews::SuggestedButton::GetPreferredSize() const { |
| ui::ResourceBundle& rb = ui::ResourceBundle::GetSharedInstance(); |
| gfx::Size size = rb.GetImageNamed(ResourceIDForState()).Size(); |
| const gfx::Insets insets = GetInsets(); |
| size.Enlarge(insets.width(), insets.height()); |
| return size; |
| } |
| |
| const char* AutofillDialogViews::SuggestedButton::GetClassName() const { |
| return kSuggestedButtonClassName; |
| } |
| |
| void AutofillDialogViews::SuggestedButton::PaintChildren( |
| gfx::Canvas* canvas, |
| const views::CullSet& cull_set) { |
| } |
| |
| void AutofillDialogViews::SuggestedButton::OnPaint(gfx::Canvas* canvas) { |
| ui::ResourceBundle& rb = ui::ResourceBundle::GetSharedInstance(); |
| const gfx::Insets insets = GetInsets(); |
| canvas->DrawImageInt(*rb.GetImageSkiaNamed(ResourceIDForState()), |
| insets.left(), insets.top()); |
| views::Painter::PaintFocusPainter(this, canvas, focus_painter()); |
| } |
| |
| int AutofillDialogViews::SuggestedButton::ResourceIDForState() const { |
| views::Button::ButtonState button_state = state(); |
| if (button_state == views::Button::STATE_PRESSED) |
| return IDR_AUTOFILL_DIALOG_MENU_BUTTON_P; |
| else if (button_state == views::Button::STATE_HOVERED) |
| return IDR_AUTOFILL_DIALOG_MENU_BUTTON_H; |
| else if (button_state == views::Button::STATE_DISABLED) |
| return IDR_AUTOFILL_DIALOG_MENU_BUTTON_D; |
| DCHECK_EQ(views::Button::STATE_NORMAL, button_state); |
| return IDR_AUTOFILL_DIALOG_MENU_BUTTON; |
| } |
| |
| // AutofillDialogViews::DetailsContainerView ----------------------------------- |
| |
| AutofillDialogViews::DetailsContainerView::DetailsContainerView( |
| const base::Closure& callback) |
| : bounds_changed_callback_(callback), |
| ignore_layouts_(false) {} |
| |
| AutofillDialogViews::DetailsContainerView::~DetailsContainerView() {} |
| |
| void AutofillDialogViews::DetailsContainerView::OnBoundsChanged( |
| const gfx::Rect& previous_bounds) { |
| bounds_changed_callback_.Run(); |
| } |
| |
| void AutofillDialogViews::DetailsContainerView::Layout() { |
| if (!ignore_layouts_) |
| views::View::Layout(); |
| } |
| |
| // AutofillDialogViews::SuggestionView ----------------------------------------- |
| |
| AutofillDialogViews::SuggestionView::SuggestionView( |
| AutofillDialogViews* autofill_dialog) |
| : label_(new views::Label()), |
| label_line_2_(new views::Label()), |
| icon_(new views::ImageView()), |
| textfield_( |
| new ExpandingTextfield(base::string16(), |
| base::string16(), |
| false, |
| autofill_dialog)) { |
| // TODO(estade): Make this the correct color. |
| SetBorder(views::Border::CreateSolidSidedBorder(1, 0, 0, 0, SK_ColorLTGRAY)); |
| |
| SectionRowView* label_container = new SectionRowView(); |
| AddChildView(label_container); |
| |
| // Label and icon. |
| label_container->AddChildView(icon_); |
| label_->SetHorizontalAlignment(gfx::ALIGN_LEFT); |
| label_container->AddChildView(label_); |
| |
| // TODO(estade): get the sizing and spacing right on this textfield. |
| textfield_->SetVisible(false); |
| textfield_->SetDefaultWidthInCharacters(15); |
| label_container->AddChildView(textfield_); |
| |
| label_line_2_->SetHorizontalAlignment(gfx::ALIGN_LEFT); |
| label_line_2_->SetVisible(false); |
| label_line_2_->SetLineHeight(22); |
| label_line_2_->SetMultiLine(true); |
| AddChildView(label_line_2_); |
| |
| SetLayoutManager(new views::BoxLayout(views::BoxLayout::kVertical, 0, 0, 7)); |
| } |
| |
| AutofillDialogViews::SuggestionView::~SuggestionView() {} |
| |
| gfx::Size AutofillDialogViews::SuggestionView::GetPreferredSize() const { |
| // There's no preferred width. The parent's layout should get the preferred |
| // height from GetHeightForWidth(). |
| return gfx::Size(); |
| } |
| |
| int AutofillDialogViews::SuggestionView::GetHeightForWidth(int width) const { |
| int height = 0; |
| CanUseVerticallyCompactText(width, &height); |
| return height; |
| } |
| |
| bool AutofillDialogViews::SuggestionView::CanUseVerticallyCompactText( |
| int available_width, |
| int* resulting_height) const { |
| // This calculation may be costly, avoid doing it more than once per width. |
| if (!calculated_heights_.count(available_width)) { |
| // Changing the state of |this| now will lead to extra layouts and |
| // paints we don't want, so create another SuggestionView to calculate |
| // which label we have room to show. |
| SuggestionView sizing_view(NULL); |
| sizing_view.SetLabelText(state_.vertically_compact_text); |
| sizing_view.SetIcon(state_.icon); |
| sizing_view.SetTextfield(state_.extra_text, state_.extra_icon); |
| sizing_view.label_->SetSize(gfx::Size(available_width, 0)); |
| sizing_view.label_line_2_->SetSize(gfx::Size(available_width, 0)); |
| |
| // Shortcut |sizing_view|'s GetHeightForWidth() to avoid an infinite loop. |
| // Its BoxLayout must do these calculations for us. |
| views::LayoutManager* layout = sizing_view.GetLayoutManager(); |
| if (layout->GetPreferredSize(&sizing_view).width() <= available_width) { |
| calculated_heights_[available_width] = std::make_pair( |
| true, |
| layout->GetPreferredHeightForWidth(&sizing_view, available_width)); |
| } else { |
| sizing_view.SetLabelText(state_.horizontally_compact_text); |
| calculated_heights_[available_width] = std::make_pair( |
| false, |
| layout->GetPreferredHeightForWidth(&sizing_view, available_width)); |
| } |
| } |
| |
| const std::pair<bool, int>& values = calculated_heights_[available_width]; |
| *resulting_height = values.second; |
| return values.first; |
| } |
| |
| void AutofillDialogViews::SuggestionView::OnBoundsChanged( |
| const gfx::Rect& previous_bounds) { |
| UpdateLabelText(); |
| } |
| |
| void AutofillDialogViews::SuggestionView::SetState( |
| const SuggestionState& state) { |
| calculated_heights_.clear(); |
| state_ = state; |
| SetVisible(state_.visible); |
| UpdateLabelText(); |
| SetIcon(state_.icon); |
| SetTextfield(state_.extra_text, state_.extra_icon); |
| PreferredSizeChanged(); |
| } |
| |
| void AutofillDialogViews::SuggestionView::SetLabelText( |
| const base::string16& text) { |
| // TODO(estade): does this localize well? |
| base::string16 line_return(base::ASCIIToUTF16("\n")); |
| size_t position = text.find(line_return); |
| if (position == base::string16::npos) { |
| label_->SetText(text); |
| label_line_2_->SetVisible(false); |
| } else { |
| label_->SetText(text.substr(0, position)); |
| label_line_2_->SetText(text.substr(position + line_return.length())); |
| label_line_2_->SetVisible(true); |
| } |
| } |
| |
| void AutofillDialogViews::SuggestionView::SetIcon( |
| const gfx::Image& image) { |
| icon_->SetVisible(!image.IsEmpty()); |
| icon_->SetImage(image.AsImageSkia()); |
| } |
| |
| void AutofillDialogViews::SuggestionView::SetTextfield( |
| const base::string16& placeholder_text, |
| const gfx::Image& icon) { |
| textfield_->SetPlaceholderText(placeholder_text); |
| textfield_->SetIcon(icon); |
| textfield_->SetVisible(!placeholder_text.empty()); |
| } |
| |
| void AutofillDialogViews::SuggestionView::UpdateLabelText() { |
| int unused; |
| SetLabelText(CanUseVerticallyCompactText(width(), &unused) ? |
| state_.vertically_compact_text : |
| state_.horizontally_compact_text); |
| } |
| |
| // AutofillDialogView ---------------------------------------------------------- |
| |
| // static |
| AutofillDialogView* AutofillDialogView::Create( |
| AutofillDialogViewDelegate* delegate) { |
| return new AutofillDialogViews(delegate); |
| } |
| |
| // AutofillDialogViews --------------------------------------------------------- |
| |
| AutofillDialogViews::AutofillDialogViews(AutofillDialogViewDelegate* delegate) |
| : delegate_(delegate), |
| updates_scope_(0), |
| needs_update_(false), |
| window_(NULL), |
| notification_area_(NULL), |
| account_chooser_(NULL), |
| sign_in_web_view_(NULL), |
| scrollable_area_(NULL), |
| details_container_(NULL), |
| loading_shield_(NULL), |
| loading_shield_height_(0), |
| overlay_view_(NULL), |
| button_strip_extra_view_(NULL), |
| save_in_chrome_checkbox_(NULL), |
| save_in_chrome_checkbox_container_(NULL), |
| button_strip_image_(NULL), |
| footnote_view_(NULL), |
| legal_document_view_(NULL), |
| focus_manager_(NULL), |
| error_bubble_(NULL), |
| observer_(this) { |
| DCHECK(delegate); |
| detail_groups_.insert(std::make_pair(SECTION_CC, |
| DetailsGroup(SECTION_CC))); |
| detail_groups_.insert(std::make_pair(SECTION_BILLING, |
| DetailsGroup(SECTION_BILLING))); |
| detail_groups_.insert(std::make_pair(SECTION_CC_BILLING, |
| DetailsGroup(SECTION_CC_BILLING))); |
| detail_groups_.insert(std::make_pair(SECTION_SHIPPING, |
| DetailsGroup(SECTION_SHIPPING))); |
| } |
| |
| AutofillDialogViews::~AutofillDialogViews() { |
| HideErrorBubble(); |
| DCHECK(!window_); |
| } |
| |
| void AutofillDialogViews::Show() { |
| InitChildViews(); |
| UpdateAccountChooser(); |
| UpdateNotificationArea(); |
| UpdateButtonStripExtraView(); |
| |
| window_ = ShowWebModalDialogViews(this, delegate_->GetWebContents()); |
| focus_manager_ = window_->GetFocusManager(); |
| focus_manager_->AddFocusChangeListener(this); |
| |
| ShowDialogInMode(DETAIL_INPUT); |
| |
| // Listen for size changes on the browser. |
| views::Widget* browser_widget = |
| views::Widget::GetTopLevelWidgetForNativeView( |
| delegate_->GetWebContents()->GetNativeView()); |
| observer_.Add(browser_widget); |
| |
| // Listen for unhandled mouse presses on the non-client view. |
| event_handler_.reset(new MousePressedHandler(delegate_)); |
| window_->GetRootView()->AddPostTargetHandler(event_handler_.get()); |
| observer_.Add(window_); |
| } |
| |
| void AutofillDialogViews::Hide() { |
| if (window_) |
| window_->Close(); |
| } |
| |
| void AutofillDialogViews::UpdatesStarted() { |
| updates_scope_++; |
| } |
| |
| void AutofillDialogViews::UpdatesFinished() { |
| updates_scope_--; |
| DCHECK_GE(updates_scope_, 0); |
| if (updates_scope_ == 0 && needs_update_) { |
| needs_update_ = false; |
| ContentsPreferredSizeChanged(); |
| } |
| } |
| |
| void AutofillDialogViews::UpdateAccountChooser() { |
| account_chooser_->Update(); |
| |
| bool show_loading = delegate_->ShouldShowSpinner(); |
| if (show_loading != loading_shield_->visible()) { |
| if (show_loading) { |
| loading_shield_height_ = std::max(kInitialLoadingShieldHeight, |
| GetContentsBounds().height()); |
| ShowDialogInMode(LOADING); |
| } else { |
| bool show_sign_in = delegate_->ShouldShowSignInWebView(); |
| ShowDialogInMode(show_sign_in ? SIGN_IN : DETAIL_INPUT); |
| } |
| |
| InvalidateLayout(); |
| ContentsPreferredSizeChanged(); |
| } |
| |
| // Update legal documents for the account. |
| if (footnote_view_) { |
| const base::string16 text = delegate_->LegalDocumentsText(); |
| legal_document_view_->SetText(text); |
| |
| if (!text.empty()) { |
| const std::vector<gfx::Range>& link_ranges = |
| delegate_->LegalDocumentLinks(); |
| for (size_t i = 0; i < link_ranges.size(); ++i) { |
| views::StyledLabel::RangeStyleInfo link_range_info = |
| views::StyledLabel::RangeStyleInfo::CreateForLink(); |
| link_range_info.disable_line_wrapping = false; |
| legal_document_view_->AddStyleRange(link_ranges[i], link_range_info); |
| } |
| } |
| |
| footnote_view_->SetVisible(!text.empty()); |
| ContentsPreferredSizeChanged(); |
| } |
| |
| if (GetWidget()) |
| GetWidget()->UpdateWindowTitle(); |
| } |
| |
| void AutofillDialogViews::UpdateButtonStrip() { |
| button_strip_extra_view_->SetVisible( |
| GetDialogButtons() != ui::DIALOG_BUTTON_NONE); |
| UpdateButtonStripExtraView(); |
| GetDialogClientView()->UpdateDialogButtons(); |
| |
| ContentsPreferredSizeChanged(); |
| } |
| |
| void AutofillDialogViews::UpdateOverlay() { |
| overlay_view_->UpdateState(); |
| ContentsPreferredSizeChanged(); |
| } |
| |
| void AutofillDialogViews::UpdateDetailArea() { |
| scrollable_area_->SetVisible(true); |
| ContentsPreferredSizeChanged(); |
| } |
| |
| void AutofillDialogViews::UpdateForErrors() { |
| ValidateForm(); |
| } |
| |
| void AutofillDialogViews::UpdateNotificationArea() { |
| DCHECK(notification_area_); |
| notification_area_->SetNotifications(delegate_->CurrentNotifications()); |
| ContentsPreferredSizeChanged(); |
| } |
| |
| void AutofillDialogViews::UpdateSection(DialogSection section) { |
| UpdateSectionImpl(section, true); |
| } |
| |
| void AutofillDialogViews::UpdateErrorBubble() { |
| if (!delegate_->ShouldShowErrorBubble()) |
| HideErrorBubble(); |
| } |
| |
| void AutofillDialogViews::FillSection(DialogSection section, |
| ServerFieldType originating_type) { |
| DetailsGroup* group = GroupForSection(section); |
| // Make sure to overwrite the originating input if it exists. |
| TextfieldMap::iterator text_mapping = |
| group->textfields.find(originating_type); |
| if (text_mapping != group->textfields.end()) |
| text_mapping->second->SetText(base::string16()); |
| |
| // If the Autofill data comes from a credit card, make sure to overwrite the |
| // CC comboboxes (even if they already have something in them). If the |
| // Autofill data comes from an AutofillProfile, leave the comboboxes alone. |
| if (section == GetCreditCardSection() && |
| AutofillType(originating_type).group() == CREDIT_CARD) { |
| for (ComboboxMap::const_iterator it = group->comboboxes.begin(); |
| it != group->comboboxes.end(); ++it) { |
| if (AutofillType(it->first).group() == CREDIT_CARD) |
| it->second->SetSelectedIndex(it->second->model()->GetDefaultIndex()); |
| } |
| } |
| |
| UpdateSectionImpl(section, false); |
| } |
| |
| void AutofillDialogViews::GetUserInput(DialogSection section, |
| FieldValueMap* output) { |
| DetailsGroup* group = GroupForSection(section); |
| for (TextfieldMap::const_iterator it = group->textfields.begin(); |
| it != group->textfields.end(); ++it) { |
| output->insert(std::make_pair(it->first, it->second->GetText())); |
| } |
| for (ComboboxMap::const_iterator it = group->comboboxes.begin(); |
| it != group->comboboxes.end(); ++it) { |
| output->insert(std::make_pair(it->first, |
| it->second->model()->GetItemAt(it->second->selected_index()))); |
| } |
| } |
| |
| base::string16 AutofillDialogViews::GetCvc() { |
| return GroupForSection(GetCreditCardSection())->suggested_info-> |
| textfield()->GetText(); |
| } |
| |
| bool AutofillDialogViews::SaveDetailsLocally() { |
| DCHECK(save_in_chrome_checkbox_->visible()); |
| return save_in_chrome_checkbox_->checked(); |
| } |
| |
| const content::NavigationController* AutofillDialogViews::ShowSignIn() { |
| // The initial minimum width and height are set such that the dialog |
| // won't change size before the page is loaded. |
| int min_width = GetContentsBounds().width(); |
| // The height has to include the button strip. |
| int min_height = GetDialogClientView()->GetContentsBounds().height(); |
| |
| // TODO(abodenha): We should be able to use the WebContents of the WebView |
| // to navigate instead of LoadInitialURL. Figure out why it doesn't work. |
| sign_in_delegate_.reset( |
| new AutofillDialogSignInDelegate( |
| this, |
| sign_in_web_view_->GetWebContents(), |
| delegate_->GetWebContents(), |
| gfx::Size(min_width, min_height), GetMaximumSignInViewSize())); |
| sign_in_web_view_->LoadInitialURL(delegate_->SignInUrl()); |
| |
| ShowDialogInMode(SIGN_IN); |
| |
| ContentsPreferredSizeChanged(); |
| |
| return &sign_in_web_view_->web_contents()->GetController(); |
| } |
| |
| void AutofillDialogViews::HideSignIn() { |
| sign_in_web_view_->SetWebContents(NULL); |
| |
| if (delegate_->ShouldShowSpinner()) { |
| UpdateAccountChooser(); |
| } else { |
| ShowDialogInMode(DETAIL_INPUT); |
| InvalidateLayout(); |
| } |
| DCHECK(!sign_in_web_view_->visible()); |
| |
| ContentsPreferredSizeChanged(); |
| } |
| |
| void AutofillDialogViews::ModelChanged() { |
| menu_runner_.reset(); |
| |
| for (DetailGroupMap::const_iterator iter = detail_groups_.begin(); |
| iter != detail_groups_.end(); ++iter) { |
| UpdateDetailsGroupState(iter->second); |
| } |
| } |
| |
| void AutofillDialogViews::OnSignInResize(const gfx::Size& pref_size) { |
| sign_in_web_view_->SetPreferredSize(pref_size); |
| ContentsPreferredSizeChanged(); |
| } |
| |
| void AutofillDialogViews::ValidateSection(DialogSection section) { |
| ValidateGroup(*GroupForSection(section), VALIDATE_EDIT); |
| } |
| |
| gfx::Size AutofillDialogViews::GetPreferredSize() const { |
| if (preferred_size_.IsEmpty()) |
| preferred_size_ = CalculatePreferredSize(false); |
| |
| return preferred_size_; |
| } |
| |
| gfx::Size AutofillDialogViews::GetMinimumSize() const { |
| return CalculatePreferredSize(true); |
| } |
| |
| void AutofillDialogViews::Layout() { |
| const gfx::Rect content_bounds = GetContentsBounds(); |
| if (sign_in_web_view_->visible()) { |
| sign_in_web_view_->SetBoundsRect(content_bounds); |
| return; |
| } |
| |
| if (loading_shield_->visible()) { |
| loading_shield_->SetBoundsRect(bounds()); |
| return; |
| } |
| |
| const int x = content_bounds.x(); |
| const int y = content_bounds.y(); |
| const int width = content_bounds.width(); |
| // Layout notification area at top of dialog. |
| int notification_height = notification_area_->GetHeightForWidth(width); |
| notification_area_->SetBounds(x, y, width, notification_height); |
| |
| // The rest (the |scrollable_area_|) takes up whatever's left. |
| if (scrollable_area_->visible()) { |
| int scroll_y = y; |
| if (notification_height > notification_area_->GetInsets().height()) |
| scroll_y += notification_height + views::kRelatedControlVerticalSpacing; |
| |
| int scroll_bottom = content_bounds.bottom(); |
| DCHECK_EQ(scrollable_area_->contents(), details_container_); |
| details_container_->SizeToPreferredSize(); |
| details_container_->Layout(); |
| // TODO(estade): remove this hack. See crbug.com/285996 |
| details_container_->set_ignore_layouts(true); |
| scrollable_area_->SetBounds(x, scroll_y, width, scroll_bottom - scroll_y); |
| details_container_->set_ignore_layouts(false); |
| } |
| |
| if (error_bubble_) |
| error_bubble_->UpdatePosition(); |
| } |
| |
| void AutofillDialogViews::OnNativeThemeChanged( |
| const ui::NativeTheme* theme) { |
| if (!legal_document_view_) |
| return; |
| |
| // NOTE: This color may change because of |auto_color_readability|, set on |
| // |legal_document_view_|. |
| views::StyledLabel::RangeStyleInfo default_style; |
| default_style.color = |
| theme->GetSystemColor(ui::NativeTheme::kColorId_LabelDisabledColor); |
| |
| legal_document_view_->SetDefaultStyle(default_style); |
| } |
| |
| ui::ModalType AutofillDialogViews::GetModalType() const { |
| return ui::MODAL_TYPE_CHILD; |
| } |
| |
| base::string16 AutofillDialogViews::GetWindowTitle() const { |
| base::string16 title = delegate_->DialogTitle(); |
| // Hack alert: we don't want the dialog to jiggle when a title is added or |
| // removed. Setting a non-empty string here keeps the dialog's title bar the |
| // same size. |
| return title.empty() ? base::ASCIIToUTF16(" ") : title; |
| } |
| |
| void AutofillDialogViews::WindowClosing() { |
| focus_manager_->RemoveFocusChangeListener(this); |
| } |
| |
| void AutofillDialogViews::DeleteDelegate() { |
| window_ = NULL; |
| // |this| belongs to the controller (|delegate_|). |
| delegate_->ViewClosed(); |
| } |
| |
| int AutofillDialogViews::GetDialogButtons() const { |
| return delegate_->GetDialogButtons(); |
| } |
| |
| int AutofillDialogViews::GetDefaultDialogButton() const { |
| if (GetDialogButtons() & ui::DIALOG_BUTTON_OK) |
| return ui::DIALOG_BUTTON_OK; |
| |
| return ui::DIALOG_BUTTON_NONE; |
| } |
| |
| base::string16 AutofillDialogViews::GetDialogButtonLabel( |
| ui::DialogButton button) const { |
| return button == ui::DIALOG_BUTTON_OK ? |
| delegate_->ConfirmButtonText() : delegate_->CancelButtonText(); |
| } |
| |
| bool AutofillDialogViews::ShouldDefaultButtonBeBlue() const { |
| return true; |
| } |
| |
| bool AutofillDialogViews::IsDialogButtonEnabled(ui::DialogButton button) const { |
| return delegate_->IsDialogButtonEnabled(button); |
| } |
| |
| views::View* AutofillDialogViews::GetInitiallyFocusedView() { |
| if (!window_ || !focus_manager_) |
| return NULL; |
| |
| if (sign_in_web_view_->visible()) |
| return sign_in_web_view_; |
| |
| if (loading_shield_->visible()) |
| return views::DialogDelegateView::GetInitiallyFocusedView(); |
| |
| DCHECK(scrollable_area_->visible()); |
| |
| views::FocusManager* manager = focus_manager_; |
| for (views::View* next = scrollable_area_; |
| next; |
| next = manager->GetNextFocusableView(next, window_, false, true)) { |
| views::View* input_view = GetAncestralInputView(next); |
| if (!input_view) |
| continue; |
| |
| // If there are no invalid inputs, return the first input found. Otherwise, |
| // return the first invalid input found. |
| if (validity_map_.empty() || |
| validity_map_.find(input_view) != validity_map_.end()) { |
| return next; |
| } |
| } |
| |
| return views::DialogDelegateView::GetInitiallyFocusedView(); |
| } |
| |
| views::View* AutofillDialogViews::CreateExtraView() { |
| return button_strip_extra_view_; |
| } |
| |
| views::View* AutofillDialogViews::CreateTitlebarExtraView() { |
| return account_chooser_; |
| } |
| |
| views::View* AutofillDialogViews::CreateFootnoteView() { |
| footnote_view_ = new LayoutPropagationView(); |
| footnote_view_->SetLayoutManager( |
| new views::BoxLayout(views::BoxLayout::kVertical, |
| kDialogEdgePadding, |
| kDialogEdgePadding, |
| 0)); |
| footnote_view_->SetBorder( |
| views::Border::CreateSolidSidedBorder(1, 0, 0, 0, kSubtleBorderColor)); |
| footnote_view_->set_background( |
| views::Background::CreateSolidBackground(kShadingColor)); |
| |
| legal_document_view_ = new views::StyledLabel(base::string16(), this); |
| |
| footnote_view_->AddChildView(legal_document_view_); |
| footnote_view_->SetVisible(false); |
| |
| return footnote_view_; |
| } |
| |
| views::View* AutofillDialogViews::CreateOverlayView() { |
| return overlay_view_; |
| } |
| |
| bool AutofillDialogViews::Cancel() { |
| return delegate_->OnCancel(); |
| } |
| |
| bool AutofillDialogViews::Accept() { |
| if (ValidateForm()) |
| return delegate_->OnAccept(); |
| |
| // |ValidateForm()| failed; there should be invalid views in |validity_map_|. |
| DCHECK(!validity_map_.empty()); |
| FocusInitialView(); |
| |
| return false; |
| } |
| |
| void AutofillDialogViews::ContentsChanged(views::Textfield* sender, |
| const base::string16& new_contents) { |
| InputEditedOrActivated(TypeForTextfield(sender), |
| sender->GetBoundsInScreen(), |
| true); |
| |
| const ExpandingTextfield* expanding = static_cast<ExpandingTextfield*>( |
| sender->GetAncestorWithClassName(ExpandingTextfield::kViewClassName)); |
| if (expanding && expanding->needs_layout()) |
| ContentsPreferredSizeChanged(); |
| } |
| |
| bool AutofillDialogViews::HandleKeyEvent(views::Textfield* sender, |
| const ui::KeyEvent& key_event) { |
| ui::KeyEvent copy(key_event); |
| content::NativeWebKeyboardEvent event(©); |
| return delegate_->HandleKeyPressEventInInput(event); |
| } |
| |
| bool AutofillDialogViews::HandleMouseEvent(views::Textfield* sender, |
| const ui::MouseEvent& mouse_event) { |
| if (mouse_event.IsLeftMouseButton() && sender->HasFocus()) { |
| InputEditedOrActivated(TypeForTextfield(sender), |
| sender->GetBoundsInScreen(), |
| false); |
| // Show an error bubble if a user clicks on an input that's already focused |
| // (and invalid). |
| ShowErrorBubbleForViewIfNecessary(sender); |
| } |
| |
| return false; |
| } |
| |
| void AutofillDialogViews::OnWillChangeFocus( |
| views::View* focused_before, |
| views::View* focused_now) { |
| delegate_->FocusMoved(); |
| HideErrorBubble(); |
| } |
| |
| void AutofillDialogViews::OnDidChangeFocus( |
| views::View* focused_before, |
| views::View* focused_now) { |
| // If user leaves an edit-field, revalidate the group it belongs to. |
| if (focused_before) { |
| DetailsGroup* group = GroupForView(focused_before); |
| if (group && group->container->visible()) |
| ValidateGroup(*group, VALIDATE_EDIT); |
| } |
| |
| // Show an error bubble when the user focuses the input. |
| if (focused_now) { |
| focused_now->ScrollRectToVisible(focused_now->GetLocalBounds()); |
| ShowErrorBubbleForViewIfNecessary(focused_now); |
| } |
| } |
| |
| void AutofillDialogViews::OnPerformAction(views::Combobox* combobox) { |
| DialogSection section = GroupForView(combobox)->section; |
| InputEditedOrActivated(TypeForCombobox(combobox), gfx::Rect(), true); |
| // NOTE: |combobox| may have been deleted. |
| ValidateGroup(*GroupForSection(section), VALIDATE_EDIT); |
| SetEditabilityForSection(section); |
| } |
| |
| void AutofillDialogViews::StyledLabelLinkClicked(const gfx::Range& range, |
| int event_flags) { |
| delegate_->LegalDocumentLinkClicked(range); |
| } |
| |
| void AutofillDialogViews::OnMenuButtonClicked(views::View* source, |
| const gfx::Point& point) { |
| DCHECK_EQ(kSuggestedButtonClassName, source->GetClassName()); |
| |
| DetailsGroup* group = NULL; |
| for (DetailGroupMap::iterator iter = detail_groups_.begin(); |
| iter != detail_groups_.end(); ++iter) { |
| if (source == iter->second.suggested_button) { |
| group = &iter->second; |
| break; |
| } |
| } |
| DCHECK(group); |
| |
| if (!group->suggested_button->visible()) |
| return; |
| |
| menu_runner_.reset( |
| new views::MenuRunner(delegate_->MenuModelForSection(group->section), 0)); |
| |
| group->container->SetActive(true); |
| views::Button::ButtonState state = group->suggested_button->state(); |
| group->suggested_button->SetState(views::Button::STATE_PRESSED); |
| |
| gfx::Rect screen_bounds = source->GetBoundsInScreen(); |
| screen_bounds.Inset(source->GetInsets()); |
| if (menu_runner_->RunMenuAt(source->GetWidget(), |
| NULL, |
| screen_bounds, |
| views::MENU_ANCHOR_TOPRIGHT, |
| ui::MENU_SOURCE_NONE) == |
| views::MenuRunner::MENU_DELETED) { |
| return; |
| } |
| |
| group->container->SetActive(false); |
| group->suggested_button->SetState(state); |
| } |
| |
| gfx::Size AutofillDialogViews::CalculatePreferredSize( |
| bool get_minimum_size) const { |
| gfx::Insets insets = GetInsets(); |
| gfx::Size scroll_size = scrollable_area_->contents()->GetPreferredSize(); |
| // The width is always set by the scroll area. |
| const int width = scroll_size.width(); |
| |
| if (sign_in_web_view_->visible()) { |
| const gfx::Size size = static_cast<views::View*>(sign_in_web_view_)-> |
| GetPreferredSize(); |
| return gfx::Size(width + insets.width(), size.height() + insets.height()); |
| } |
| |
| if (overlay_view_->visible()) { |
| const int height = overlay_view_->GetHeightForContentsForWidth(width); |
| if (height != 0) |
| return gfx::Size(width + insets.width(), height + insets.height()); |
| } |
| |
| if (loading_shield_->visible()) { |
| return gfx::Size(width + insets.width(), |
| loading_shield_height_ + insets.height()); |
| } |
| |
| int height = 0; |
| const int notification_height = notification_area_->GetHeightForWidth(width); |
| if (notification_height > notification_area_->GetInsets().height()) |
| height += notification_height + views::kRelatedControlVerticalSpacing; |
| |
| if (scrollable_area_->visible()) |
| height += get_minimum_size ? kMinimumContentsHeight : scroll_size.height(); |
| |
| return gfx::Size(width + insets.width(), height + insets.height()); |
| } |
| |
| gfx::Size AutofillDialogViews::GetMinimumSignInViewSize() const { |
| return gfx::Size(GetDialogClientView()->size().width() - GetInsets().width(), |
| kMinimumContentsHeight); |
| } |
| |
| gfx::Size AutofillDialogViews::GetMaximumSignInViewSize() const { |
| web_modal::WebContentsModalDialogHost* dialog_host = |
| web_modal::WebContentsModalDialogManager::FromWebContents( |
| delegate_->GetWebContents())->delegate()-> |
| GetWebContentsModalDialogHost(); |
| |
| // Inset the maximum dialog height to get the maximum content height. |
| int height = dialog_host->GetMaximumDialogSize().height(); |
| const int non_client_height = GetWidget()->non_client_view()->height(); |
| const int client_height = GetWidget()->client_view()->height(); |
| // TODO(msw): Resolve the 12 pixel discrepancy; is that the bubble border? |
| height -= non_client_height - client_height - 12; |
| height = std::max(height, kMinimumContentsHeight); |
| |
| // The dialog's width never changes. |
| const int width = GetDialogClientView()->size().width() - GetInsets().width(); |
| return gfx::Size(width, height); |
| } |
| |
| DialogSection AutofillDialogViews::GetCreditCardSection() const { |
| if (delegate_->SectionIsActive(SECTION_CC)) |
| return SECTION_CC; |
| |
| DCHECK(delegate_->SectionIsActive(SECTION_CC_BILLING)); |
| return SECTION_CC_BILLING; |
| } |
| |
| void AutofillDialogViews::InitChildViews() { |
| button_strip_extra_view_ = new LayoutPropagationView(); |
| button_strip_extra_view_->SetLayoutManager( |
| new views::BoxLayout(views::BoxLayout::kHorizontal, 0, 0, 0)); |
| |
| save_in_chrome_checkbox_container_ = new views::View(); |
| save_in_chrome_checkbox_container_->SetLayoutManager( |
| new views::BoxLayout(views::BoxLayout::kHorizontal, 0, 0, 7)); |
| button_strip_extra_view_->AddChildView(save_in_chrome_checkbox_container_); |
| |
| save_in_chrome_checkbox_ = |
| new views::Checkbox(delegate_->SaveLocallyText()); |
| save_in_chrome_checkbox_->SetChecked(delegate_->ShouldSaveInChrome()); |
| save_in_chrome_checkbox_container_->AddChildView(save_in_chrome_checkbox_); |
| |
| save_in_chrome_checkbox_container_->AddChildView( |
| new TooltipIcon(delegate_->SaveLocallyTooltip())); |
| |
| button_strip_image_ = new views::ImageView(); |
| button_strip_extra_view_->AddChildView(button_strip_image_); |
| |
| account_chooser_ = new AccountChooser(delegate_); |
| notification_area_ = new NotificationArea(delegate_); |
| notification_area_->set_arrow_centering_anchor(account_chooser_->AsWeakPtr()); |
| AddChildView(notification_area_); |
| |
| scrollable_area_ = new views::ScrollView(); |
| scrollable_area_->set_hide_horizontal_scrollbar(true); |
| scrollable_area_->SetContents(CreateDetailsContainer()); |
| AddChildView(scrollable_area_); |
| |
| loading_shield_ = new LoadingAnimationView(delegate_->SpinnerText()); |
| AddChildView(loading_shield_); |
| |
| sign_in_web_view_ = new views::WebView(delegate_->profile()); |
| AddChildView(sign_in_web_view_); |
| |
| overlay_view_ = new OverlayView(delegate_); |
| overlay_view_->SetVisible(false); |
| } |
| |
| views::View* AutofillDialogViews::CreateDetailsContainer() { |
| details_container_ = new DetailsContainerView( |
| base::Bind(&AutofillDialogViews::DetailsContainerBoundsChanged, |
| base::Unretained(this))); |
| |
| // A box layout is used because it respects widget visibility. |
| details_container_->SetLayoutManager( |
| new views::BoxLayout(views::BoxLayout::kVertical, 0, 0, 0)); |
| for (DetailGroupMap::iterator iter = detail_groups_.begin(); |
| iter != detail_groups_.end(); ++iter) { |
| CreateDetailsSection(iter->second.section); |
| details_container_->AddChildView(iter->second.container); |
| } |
| |
| return details_container_; |
| } |
| |
| void AutofillDialogViews::CreateDetailsSection(DialogSection section) { |
| // Inputs container (manual inputs + combobox). |
| views::View* inputs_container = CreateInputsContainer(section); |
| |
| DetailsGroup* group = GroupForSection(section); |
| // Container (holds label + inputs). |
| group->container = new SectionContainer(delegate_->LabelForSection(section), |
| inputs_container, |
| group->suggested_button); |
| DCHECK(group->suggested_button->parent()); |
| UpdateDetailsGroupState(*group); |
| } |
| |
| views::View* AutofillDialogViews::CreateInputsContainer(DialogSection section) { |
| // The |info_view| holds |manual_inputs| and |suggested_info|, allowing the |
| // dialog to toggle which is shown. |
| views::View* info_view = new views::View(); |
| info_view->SetLayoutManager( |
| new views::BoxLayout(views::BoxLayout::kVertical, 0, 0, 0)); |
| |
| DetailsGroup* group = GroupForSection(section); |
| group->manual_input = new views::View(); |
| InitInputsView(section); |
| info_view->AddChildView(group->manual_input); |
| |
| group->suggested_info = new SuggestionView(this); |
| info_view->AddChildView(group->suggested_info); |
| |
| // TODO(estade): It might be slightly more OO if this button were created |
| // and listened to by the section container. |
| group->suggested_button = new SuggestedButton(this); |
| |
| return info_view; |
| } |
| |
| // TODO(estade): we should be using Chrome-style constrained window padding |
| // values. |
| void AutofillDialogViews::InitInputsView(DialogSection section) { |
| DetailsGroup* group = GroupForSection(section); |
| EraseInvalidViewsInGroup(group); |
| |
| TextfieldMap* textfields = &group->textfields; |
| textfields->clear(); |
| |
| ComboboxMap* comboboxes = &group->comboboxes; |
| comboboxes->clear(); |
| |
| views::View* view = group->manual_input; |
| view->RemoveAllChildViews(true); |
| |
| views::GridLayout* layout = new views::GridLayout(view); |
| view->SetLayoutManager(layout); |
| |
| int column_set_id = 0; |
| const DetailInputs& inputs = delegate_->RequestedFieldsForSection(section); |
| for (DetailInputs::const_iterator it = inputs.begin(); |
| it != inputs.end(); ++it) { |
| const DetailInput& input = *it; |
| |
| ui::ComboboxModel* input_model = |
| delegate_->ComboboxModelForAutofillType(input.type); |
| scoped_ptr<views::View> view_to_add; |
| if (input_model) { |
| views::Combobox* combobox = new views::Combobox(input_model); |
| combobox->set_listener(this); |
| comboboxes->insert(std::make_pair(input.type, combobox)); |
| SelectComboboxValueOrSetToDefault(combobox, input.initial_value); |
| view_to_add.reset(combobox); |
| } else { |
| ExpandingTextfield* field = new ExpandingTextfield(input.initial_value, |
| input.placeholder_text, |
| input.IsMultiline(), |
| this); |
| textfields->insert(std::make_pair(input.type, field)); |
| view_to_add.reset(field); |
| } |
| |
| if (input.length == DetailInput::NONE) { |
| other_owned_views_.push_back(view_to_add.release()); |
| continue; |
| } |
| |
| if (input.length == DetailInput::LONG) |
| ++column_set_id; |
| |
| views::ColumnSet* column_set = layout->GetColumnSet(column_set_id); |
| if (!column_set) { |
| // Create a new column set and row. |
| column_set = layout->AddColumnSet(column_set_id); |
| if (it != inputs.begin()) |
| layout->AddPaddingRow(0, kManualInputRowPadding); |
| layout->StartRow(0, column_set_id); |
| } else { |
| // Add a new column to existing row. |
| column_set->AddPaddingColumn(0, views::kRelatedControlHorizontalSpacing); |
| // Must explicitly skip the padding column since we've already started |
| // adding views. |
| layout->SkipColumns(1); |
| } |
| |
| float expand = input.expand_weight; |
| column_set->AddColumn(views::GridLayout::FILL, |
| views::GridLayout::FILL, |
| expand ? expand : 1.0, |
| views::GridLayout::USE_PREF, |
| 0, |
| 0); |
| |
| // This is the same as AddView(view_to_add), except that 1 is used for the |
| // view's preferred width. Thus the width of the column completely depends |
| // on |expand|. |
| layout->AddView(view_to_add.release(), 1, 1, |
| views::GridLayout::FILL, views::GridLayout::FILL, |
| 1, 0); |
| |
| if (input.length == DetailInput::LONG || |
| input.length == DetailInput::SHORT_EOL) { |
| ++column_set_id; |
| } |
| } |
| |
| SetIconsForSection(section); |
| } |
| |
| void AutofillDialogViews::ShowDialogInMode(DialogMode dialog_mode) { |
| loading_shield_->SetVisible(dialog_mode == LOADING); |
| sign_in_web_view_->SetVisible(dialog_mode == SIGN_IN); |
| notification_area_->SetVisible(dialog_mode == DETAIL_INPUT); |
| scrollable_area_->SetVisible(dialog_mode == DETAIL_INPUT); |
| FocusInitialView(); |
| } |
| |
| void AutofillDialogViews::UpdateSectionImpl( |
| DialogSection section, |
| bool clobber_inputs) { |
| DetailsGroup* group = GroupForSection(section); |
| |
| if (clobber_inputs) { |
| ServerFieldType type = UNKNOWN_TYPE; |
| views::View* focused = GetFocusManager()->GetFocusedView(); |
| if (focused && group->container->Contains(focused)) { |
| // Remember which view was focused before the inputs are clobbered. |
| if (focused->GetClassName() == ExpandingTextfield::kViewClassName) |
| type = TypeForTextfield(focused); |
| else if (focused->GetClassName() == views::Combobox::kViewClassName) |
| type = TypeForCombobox(static_cast<views::Combobox*>(focused)); |
| } |
| |
| InitInputsView(section); |
| |
| if (type != UNKNOWN_TYPE) { |
| // Restore the focus to the input with the previous type (e.g. country). |
| views::View* to_focus = TextfieldForType(type); |
| if (!to_focus) to_focus = ComboboxForType(type); |
| if (to_focus) |
| to_focus->RequestFocus(); |
| } |
| } else { |
| const DetailInputs& updated_inputs = |
| delegate_->RequestedFieldsForSection(section); |
| |
| for (DetailInputs::const_iterator iter = updated_inputs.begin(); |
| iter != updated_inputs.end(); ++iter) { |
| const DetailInput& input = *iter; |
| |
| TextfieldMap::iterator text_mapping = group->textfields.find(input.type); |
| if (text_mapping != group->textfields.end()) { |
| ExpandingTextfield* textfield = text_mapping->second; |
| if (textfield->GetText().empty()) |
| textfield->SetText(input.initial_value); |
| } |
| |
| ComboboxMap::iterator combo_mapping = group->comboboxes.find(input.type); |
| if (combo_mapping != group->comboboxes.end()) { |
| views::Combobox* combobox = combo_mapping->second; |
| if (combobox->selected_index() == combobox->model()->GetDefaultIndex()) |
| SelectComboboxValueOrSetToDefault(combobox, input.initial_value); |
| } |
| } |
| |
| SetIconsForSection(section); |
| } |
| |
| SetEditabilityForSection(section); |
| UpdateDetailsGroupState(*group); |
| } |
| |
| void AutofillDialogViews::UpdateDetailsGroupState(const DetailsGroup& group) { |
| const SuggestionState& suggestion_state = |
| delegate_->SuggestionStateForSection(group.section); |
| group.suggested_info->SetState(suggestion_state); |
| group.manual_input->SetVisible(!suggestion_state.visible); |
| |
| UpdateButtonStripExtraView(); |
| |
| const bool has_menu = !!delegate_->MenuModelForSection(group.section); |
| |
| if (group.suggested_button) |
| group.suggested_button->SetVisible(has_menu); |
| |
| if (group.container) { |
| group.container->SetForwardMouseEvents( |
| has_menu && suggestion_state.visible); |
| group.container->SetVisible(delegate_->SectionIsActive(group.section)); |
| if (group.container->visible()) |
| ValidateGroup(group, VALIDATE_EDIT); |
| } |
| |
| ContentsPreferredSizeChanged(); |
| } |
| |
| void AutofillDialogViews::FocusInitialView() { |
| views::View* to_focus = GetInitiallyFocusedView(); |
| if (to_focus && !to_focus->HasFocus()) |
| to_focus->RequestFocus(); |
| } |
| |
| template<class T> |
| void AutofillDialogViews::SetValidityForInput( |
| T* input, |
| const base::string16& message) { |
| bool invalid = !message.empty(); |
| input->SetInvalid(invalid); |
| |
| if (invalid) { |
| validity_map_[input] = message; |
| } else { |
| validity_map_.erase(input); |
| |
| if (error_bubble_ && |
| error_bubble_->anchor()->GetAncestorWithClassName( |
| input->GetClassName()) == input) { |
| validity_map_.erase(input); |
| HideErrorBubble(); |
| } |
| } |
| } |
| |
| void AutofillDialogViews::ShowErrorBubbleForViewIfNecessary(views::View* view) { |
| if (!view->GetWidget()) |
| return; |
| |
| if (!delegate_->ShouldShowErrorBubble()) { |
| DCHECK(!error_bubble_); |
| return; |
| } |
| |
| if (view->GetClassName() == DecoratedTextfield::kViewClassName && |
| !static_cast<DecoratedTextfield*>(view)->invalid()) { |
| return; |
| } |
| |
| views::View* input_view = GetAncestralInputView(view); |
| std::map<views::View*, base::string16>::iterator error_message = |
| validity_map_.find(input_view); |
| if (error_message != validity_map_.end()) { |
| input_view->ScrollRectToVisible(input_view->GetLocalBounds()); |
| |
| if (!error_bubble_ || error_bubble_->anchor() != view) { |
| HideErrorBubble(); |
| error_bubble_ = new InfoBubble(view, error_message->second); |
| error_bubble_->set_align_to_anchor_edge(true); |
| error_bubble_->set_preferred_width( |
| (kSectionContainerWidth - views::kRelatedControlVerticalSpacing) / 2); |
| bool show_above = view->GetClassName() == views::Combobox::kViewClassName; |
| error_bubble_->set_show_above_anchor(show_above); |
| error_bubble_->Show(); |
| observer_.Add(error_bubble_->GetWidget()); |
| } |
| } |
| } |
| |
| void AutofillDialogViews::HideErrorBubble() { |
| if (error_bubble_) |
| error_bubble_->Hide(); |
| } |
| |
| void AutofillDialogViews::MarkInputsInvalid( |
| DialogSection section, |
| const ValidityMessages& messages, |
| bool overwrite_unsure) { |
| DetailsGroup* group = GroupForSection(section); |
| DCHECK(group->container->visible()); |
| |
| if (group->manual_input->visible()) { |
| for (TextfieldMap::const_iterator iter = group->textfields.begin(); |
| iter != group->textfields.end(); ++iter) { |
| const ValidityMessage& message = |
| messages.GetMessageOrDefault(iter->first); |
| if (overwrite_unsure || message.sure) |
| SetValidityForInput(iter->second, message.text); |
| } |
| for (ComboboxMap::const_iterator iter = group->comboboxes.begin(); |
| iter != group->comboboxes.end(); ++iter) { |
| const ValidityMessage& message = |
| messages.GetMessageOrDefault(iter->first); |
| if (overwrite_unsure || message.sure) |
| SetValidityForInput(iter->second, message.text); |
| } |
| } else { |
| EraseInvalidViewsInGroup(group); |
| |
| if (section == GetCreditCardSection()) { |
| // Special case CVC as it's not part of |group->manual_input|. |
| const ValidityMessage& message = |
| messages.GetMessageOrDefault(CREDIT_CARD_VERIFICATION_CODE); |
| if (overwrite_unsure || message.sure) { |
| SetValidityForInput(group->suggested_info->textfield(), message.text); |
| } |
| } |
| } |
| } |
| |
| bool AutofillDialogViews::ValidateGroup(const DetailsGroup& group, |
| ValidationType validation_type) { |
| DCHECK(group.container->visible()); |
| |
| FieldValueMap detail_outputs; |
| |
| if (group.manual_input->visible()) { |
| for (TextfieldMap::const_iterator iter = group.textfields.begin(); |
| iter != group.textfields.end(); ++iter) { |
| if (!iter->second->editable()) |
| continue; |
| |
| detail_outputs[iter->first] = iter->second->GetText(); |
| } |
| for (ComboboxMap::const_iterator iter = group.comboboxes.begin(); |
| iter != group.comboboxes.end(); ++iter) { |
| if (!iter->second->enabled()) |
| continue; |
| |
| views::Combobox* combobox = iter->second; |
| base::string16 item = |
| combobox->model()->GetItemAt(combobox->selected_index()); |
| detail_outputs[iter->first] = item; |
| } |
| } else if (group.section == GetCreditCardSection()) { |
| ExpandingTextfield* cvc = group.suggested_info->textfield(); |
| if (cvc->visible()) |
| detail_outputs[CREDIT_CARD_VERIFICATION_CODE] = cvc->GetText(); |
| } |
| |
| ValidityMessages validity = delegate_->InputsAreValid(group.section, |
| detail_outputs); |
| MarkInputsInvalid(group.section, validity, validation_type == VALIDATE_FINAL); |
| |
| // If there are any validation errors, sure or unsure, the group is invalid. |
| return !validity.HasErrors(); |
| } |
| |
| bool AutofillDialogViews::ValidateForm() { |
| bool all_valid = true; |
| validity_map_.clear(); |
| |
| for (DetailGroupMap::iterator iter = detail_groups_.begin(); |
| iter != detail_groups_.end(); ++iter) { |
| const DetailsGroup& group = iter->second; |
| if (!group.container->visible()) |
| continue; |
| |
| if (!ValidateGroup(group, VALIDATE_FINAL)) |
| all_valid = false; |
| } |
| |
| return all_valid; |
| } |
| |
| void AutofillDialogViews::InputEditedOrActivated(ServerFieldType type, |
| const gfx::Rect& bounds, |
| bool was_edit) { |
| DCHECK_NE(UNKNOWN_TYPE, type); |
| |
| ExpandingTextfield* textfield = TextfieldForType(type); |
| views::Combobox* combobox = ComboboxForType(type); |
| |
| // Both views may be NULL if the event comes from an inactive section, which |
| // may occur when using an IME. |
| if (!combobox && !textfield) |
| return; |
| |
| DCHECK_NE(!!combobox, !!textfield); |
| DetailsGroup* group = textfield ? GroupForView(textfield) : |
| GroupForView(combobox); |
| base::string16 text = textfield ? |
| textfield->GetText() : |
| combobox->model()->GetItemAt(combobox->selected_index()); |
| DCHECK(group); |
| |
| delegate_->UserEditedOrActivatedInput(group->section, |
| type, |
| GetWidget()->GetNativeView(), |
| bounds, |
| text, |
| was_edit); |
| |
| // If the field is a textfield and is invalid, check if the text is now valid. |
| // Many fields (i.e. CC#) are invalid for most of the duration of editing, |
| // so flagging them as invalid prematurely is not helpful. However, |
| // correcting a minor mistake (i.e. a wrong CC digit) should immediately |
| // result in validation - positive user feedback. |
| if (textfield && textfield->invalid() && was_edit) { |
| SetValidityForInput( |
| textfield, |
| delegate_->InputValidityMessage( |
| group->section, type, textfield->GetText())); |
| |
| // If the field transitioned from invalid to valid, re-validate the group, |
| // since inter-field checks become meaningful with valid fields. |
| if (!textfield->invalid()) |
| ValidateGroup(*group, VALIDATE_EDIT); |
| } |
| |
| if (delegate_->FieldControlsIcons(type)) |
| SetIconsForSection(group->section); |
| |
| SetEditabilityForSection(group->section); |
| } |
| |
| void AutofillDialogViews::UpdateButtonStripExtraView() { |
| save_in_chrome_checkbox_container_->SetVisible( |
| delegate_->ShouldOfferToSaveInChrome()); |
| |
| gfx::Image image = delegate_->ButtonStripImage(); |
| button_strip_image_->SetVisible(!image.IsEmpty()); |
| button_strip_image_->SetImage(image.AsImageSkia()); |
| } |
| |
| void AutofillDialogViews::ContentsPreferredSizeChanged() { |
| if (updates_scope_ != 0) { |
| needs_update_ = true; |
| return; |
| } |
| |
| preferred_size_ = gfx::Size(); |
| |
| if (GetWidget() && delegate_ && delegate_->GetWebContents()) { |
| UpdateWebContentsModalDialogPosition( |
| GetWidget(), |
| web_modal::WebContentsModalDialogManager::FromWebContents( |
| delegate_->GetWebContents())->delegate()-> |
| GetWebContentsModalDialogHost()); |
| SetBoundsRect(bounds()); |
| } |
| } |
| |
| AutofillDialogViews::DetailsGroup* AutofillDialogViews::GroupForSection( |
| DialogSection section) { |
| return &detail_groups_.find(section)->second; |
| } |
| |
| AutofillDialogViews::DetailsGroup* AutofillDialogViews::GroupForView( |
| views::View* view) { |
| DCHECK(view); |
| |
| views::View* input_view = GetAncestralInputView(view); |
| if (!input_view) |
| return NULL; |
| |
| for (DetailGroupMap::iterator iter = detail_groups_.begin(); |
| iter != detail_groups_.end(); ++iter) { |
| DetailsGroup* group = &iter->second; |
| if (input_view->parent() == group->manual_input) |
| return group; |
| |
| // Textfields need to check a second case, since they can be suggested |
| // inputs instead of directly editable inputs. Those are accessed via |
| // |suggested_info|. |
| if (input_view == group->suggested_info->textfield()) { |
| return group; |
| } |
| } |
| |
| return NULL; |
| } |
| |
| void AutofillDialogViews::EraseInvalidViewsInGroup(const DetailsGroup* group) { |
| std::map<views::View*, base::string16>::iterator it = validity_map_.begin(); |
| while (it != validity_map_.end()) { |
| if (GroupForView(it->first) == group) |
| validity_map_.erase(it++); |
| else |
| ++it; |
| } |
| } |
| |
| ExpandingTextfield* AutofillDialogViews::TextfieldForType( |
| ServerFieldType type) { |
| if (type == CREDIT_CARD_VERIFICATION_CODE) { |
| DetailsGroup* group = GroupForSection(GetCreditCardSection()); |
| if (!group->manual_input->visible()) |
| return group->suggested_info->textfield(); |
| } |
| |
| for (DetailGroupMap::iterator iter = detail_groups_.begin(); |
| iter != detail_groups_.end(); ++iter) { |
| const DetailsGroup& group = iter->second; |
| if (!delegate_->SectionIsActive(group.section)) |
| continue; |
| |
| TextfieldMap::const_iterator text_mapping = group.textfields.find(type); |
| if (text_mapping != group.textfields.end()) |
| return text_mapping->second; |
| } |
| |
| return NULL; |
| } |
| |
| ServerFieldType AutofillDialogViews::TypeForTextfield( |
| const views::View* textfield) { |
| const views::View* expanding = |
| textfield->GetAncestorWithClassName(ExpandingTextfield::kViewClassName); |
| |
| DetailsGroup* cc_group = GroupForSection(GetCreditCardSection()); |
| if (expanding == cc_group->suggested_info->textfield()) |
| return CREDIT_CARD_VERIFICATION_CODE; |
| |
| for (DetailGroupMap::const_iterator it = detail_groups_.begin(); |
| it != detail_groups_.end(); ++it) { |
| if (!delegate_->SectionIsActive(it->second.section)) |
| continue; |
| |
| for (TextfieldMap::const_iterator text_it = it->second.textfields.begin(); |
| text_it != it->second.textfields.end(); ++text_it) { |
| if (expanding == text_it->second) |
| return text_it->first; |
| } |
| } |
| |
| return UNKNOWN_TYPE; |
| } |
| |
| views::Combobox* AutofillDialogViews::ComboboxForType( |
| ServerFieldType type) { |
| for (DetailGroupMap::iterator iter = detail_groups_.begin(); |
| iter != detail_groups_.end(); ++iter) { |
| const DetailsGroup& group = iter->second; |
| if (!delegate_->SectionIsActive(group.section)) |
| continue; |
| |
| ComboboxMap::const_iterator combo_mapping = group.comboboxes.find(type); |
| if (combo_mapping != group.comboboxes.end()) |
| return combo_mapping->second; |
| } |
| |
| return NULL; |
| } |
| |
| ServerFieldType AutofillDialogViews::TypeForCombobox( |
| const views::Combobox* combobox) const { |
| for (DetailGroupMap::const_iterator it = detail_groups_.begin(); |
| it != detail_groups_.end(); ++it) { |
| const DetailsGroup& group = it->second; |
| if (!delegate_->SectionIsActive(group.section)) |
| continue; |
| |
| for (ComboboxMap::const_iterator combo_it = group.comboboxes.begin(); |
| combo_it != group.comboboxes.end(); ++combo_it) { |
| if (combo_it->second == combobox) |
| return combo_it->first; |
| } |
| } |
| |
| return UNKNOWN_TYPE; |
| } |
| |
| void AutofillDialogViews::DetailsContainerBoundsChanged() { |
| if (error_bubble_) |
| error_bubble_->UpdatePosition(); |
| } |
| |
| void AutofillDialogViews::SetIconsForSection(DialogSection section) { |
| FieldValueMap user_input; |
| GetUserInput(section, &user_input); |
| FieldIconMap field_icons = delegate_->IconsForFields(user_input); |
| TextfieldMap* textfields = &GroupForSection(section)->textfields; |
| for (TextfieldMap::const_iterator textfield_it = textfields->begin(); |
| textfield_it != textfields->end(); |
| ++textfield_it) { |
| ServerFieldType field_type = textfield_it->first; |
| FieldIconMap::const_iterator field_icon_it = field_icons.find(field_type); |
| ExpandingTextfield* textfield = textfield_it->second; |
| if (field_icon_it != field_icons.end()) |
| textfield->SetIcon(field_icon_it->second); |
| else |
| textfield->SetTooltipIcon(delegate_->TooltipForField(field_type)); |
| } |
| } |
| |
| void AutofillDialogViews::SetEditabilityForSection(DialogSection section) { |
| const DetailInputs& inputs = |
| delegate_->RequestedFieldsForSection(section); |
| DetailsGroup* group = GroupForSection(section); |
| |
| for (DetailInputs::const_iterator iter = inputs.begin(); |
| iter != inputs.end(); ++iter) { |
| const DetailInput& input = *iter; |
| bool editable = delegate_->InputIsEditable(input, section); |
| |
| TextfieldMap::iterator text_mapping = group->textfields.find(input.type); |
| if (text_mapping != group->textfields.end()) { |
| ExpandingTextfield* textfield= text_mapping->second; |
| textfield->SetEditable(editable); |
| continue; |
| } |
| |
| ComboboxMap::iterator combo_mapping = group->comboboxes.find(input.type); |
| if (combo_mapping != group->comboboxes.end()) { |
| views::Combobox* combobox = combo_mapping->second; |
| combobox->SetEnabled(editable); |
| } |
| } |
| } |
| |
| void AutofillDialogViews::NonClientMousePressed() { |
| delegate_->FocusMoved(); |
| } |
| |
| AutofillDialogViews::DetailsGroup::DetailsGroup(DialogSection section) |
| : section(section), |
| container(NULL), |
| manual_input(NULL), |
| suggested_info(NULL), |
| suggested_button(NULL) {} |
| |
| AutofillDialogViews::DetailsGroup::~DetailsGroup() {} |
| |
| } // namespace autofill |