| // 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/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/views/autofill/decorated_textfield.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_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 "content/public/browser/web_contents_view.h" |
| #include "grit/theme_resources.h" |
| #include "grit/ui_resources.h" |
| #include "third_party/skia/include/core/SkColor.h" |
| #include "ui/base/animation/multi_animation.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/gfx/canvas.h" |
| #include "ui/gfx/path.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/image_button.h" |
| #include "ui/views/controls/button/label_button.h" |
| #include "ui/views/controls/button/label_button_border.h" |
| #include "ui/views/controls/combobox/combobox.h" |
| #include "ui/views/controls/focusable_border.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/widget/widget.h" |
| #include "ui/views/window/dialog_client_view.h" |
| |
| using web_modal::WebContentsModalDialogManager; |
| |
| namespace autofill { |
| |
| namespace { |
| |
| // The minimum useful height of the contents area of the dialog. |
| const int kMinimumContentsHeight = 100; |
| |
| // 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 kAutocheckoutStepsAreaPadding = 28; |
| const int kAutocheckoutStepInset = 20; |
| |
| const int kAutocheckoutProgressBarWidth = 375; |
| const int kAutocheckoutProgressBarHeight = 15; |
| |
| const int kArrowHeight = 7; |
| const int kArrowWidth = 2 * kArrowHeight; |
| |
| // The padding inside the edges of the dialog, in pixels. |
| const int kDialogEdgePadding = 20; |
| |
| // 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; |
| |
| // Spacing between lines of text in the overlay view. |
| const int kOverlayTextInterlineSpacing = 10; |
| |
| // Spacing below image and above text messages in overlay view. |
| const int kOverlayImageBottomMargin = 50; |
| |
| // A dimmer text color used in various parts of the dialog. TODO(estade): should |
| // this be part of NativeTheme? Currently the value is duplicated in several |
| // places. |
| const SkColor kGreyTextColor = SkColorSetRGB(102, 102, 102); |
| |
| const char kNotificationAreaClassName[] = "autofill/NotificationArea"; |
| const char kOverlayViewClassName[] = "autofill/OverlayView"; |
| |
| typedef ui::MultiAnimation::Part Part; |
| typedef ui::MultiAnimation::Parts Parts; |
| |
| // 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); |
| } |
| } |
| |
| // 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() {} |
| virtual ~SectionRowView() {} |
| |
| // views::View implementation: |
| virtual gfx::Size GetPreferredSize() OVERRIDE { |
| // Only the height matters anyway. |
| return child_at(2)->GetPreferredSize(); |
| } |
| |
| 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* decorated = child_at(2); |
| if (decorated->visible()) { |
| decorated->SizeToPreferredSize(); |
| decorated->SetX(bounds.width() - decorated->bounds().width()); |
| decorated->SetY(bounds.y()); |
| end_x = decorated->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); |
| }; |
| |
| // This view is used for the contents of the error bubble widget. |
| class ErrorBubbleContents : public views::View { |
| public: |
| explicit ErrorBubbleContents(const base::string16& message) |
| : color_(kWarningColor) { |
| set_border(views::Border::CreateEmptyBorder(kArrowHeight - 3, 0, 0, 0)); |
| |
| views::Label* label = new views::Label(); |
| label->SetText(message); |
| label->SetAutoColorReadabilityEnabled(false); |
| label->SetEnabledColor(SK_ColorWHITE); |
| label->set_border( |
| views::Border::CreateSolidSidedBorder(5, 10, 5, 10, color_)); |
| label->set_background( |
| views::Background::CreateSolidBackground(color_)); |
| SetLayoutManager(new views::FillLayout()); |
| AddChildView(label); |
| } |
| virtual ~ErrorBubbleContents() {} |
| |
| virtual void OnPaint(gfx::Canvas* canvas) OVERRIDE { |
| views::View::OnPaint(canvas); |
| DrawArrow(canvas, width() / 2.0f, color_, SK_ColorTRANSPARENT); |
| } |
| |
| private: |
| SkColor color_; |
| |
| DISALLOW_COPY_AND_ASSIGN(ErrorBubbleContents); |
| }; |
| |
| // A view that runs a callback whenever its bounds change. |
| class DetailsContainerView : public views::View { |
| public: |
| explicit DetailsContainerView(const base::Closure& callback) |
| : bounds_changed_callback_(callback) {} |
| virtual ~DetailsContainerView() {} |
| |
| // views::View implementation. |
| virtual void OnBoundsChanged(const gfx::Rect& previous_bounds) OVERRIDE { |
| bounds_changed_callback_.Run(); |
| } |
| |
| private: |
| base::Closure bounds_changed_callback_; |
| |
| DISALLOW_COPY_AND_ASSIGN(DetailsContainerView); |
| }; |
| |
| // 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 class which displays the status of an individual step in an |
| // Autocheckout flow. |
| class AutocheckoutStepProgressView : public views::View { |
| public: |
| AutocheckoutStepProgressView(const base::string16& description, |
| const gfx::Font& font, |
| const SkColor color, |
| const bool is_icon_visible) { |
| views::GridLayout* layout = new views::GridLayout(this); |
| SetLayoutManager(layout); |
| const int kColumnSetId = 0; |
| views::ColumnSet* columns = layout->AddColumnSet(kColumnSetId); |
| columns->AddColumn(views::GridLayout::LEADING, |
| views::GridLayout::CENTER, |
| 0, |
| views::GridLayout::USE_PREF, |
| 0, |
| 0); |
| columns->AddPaddingColumn(0, 8); |
| columns->AddColumn(views::GridLayout::LEADING, |
| views::GridLayout::CENTER, |
| 0, |
| views::GridLayout::USE_PREF, |
| 0, |
| 0); |
| layout->StartRow(0, kColumnSetId); |
| views::Label* label = new views::Label(); |
| label->SetHorizontalAlignment(gfx::ALIGN_LEFT); |
| label->set_border(views::Border::CreateEmptyBorder(0, 0, 0, 0)); |
| label->SetText(description); |
| label->SetFont(font); |
| label->SetEnabledColor(color); |
| |
| views::ImageView* icon = new views::ImageView(); |
| icon->SetVisible(is_icon_visible); |
| icon->SetImage(ui::ResourceBundle::GetSharedInstance().GetImageNamed( |
| IDR_WALLET_STEP_CHECK).ToImageSkia()); |
| |
| layout->AddView(icon); |
| layout->AddView(label); |
| } |
| |
| virtual ~AutocheckoutStepProgressView() {} |
| |
| private: |
| DISALLOW_COPY_AND_ASSIGN(AutocheckoutStepProgressView); |
| }; |
| |
| // A tooltip icon (just an ImageView with a tooltip). Looks like (?). |
| class TooltipIcon : public views::ImageView { |
| public: |
| explicit TooltipIcon(const base::string16& tooltip) : tooltip_(tooltip) { |
| SetImage(ui::ResourceBundle::GetSharedInstance().GetImageNamed( |
| IDR_AUTOFILL_TOOLTIP_ICON).ToImageSkia()); |
| } |
| virtual ~TooltipIcon() {} |
| |
| // views::View implementation |
| virtual bool GetTooltipText(const gfx::Point& p, |
| base::string16* tooltip) const OVERRIDE { |
| *tooltip = tooltip_; |
| return !tooltip_.empty(); |
| } |
| |
| private: |
| base::string16 tooltip_; |
| |
| DISALLOW_COPY_AND_ASSIGN(TooltipIcon); |
| }; |
| |
| // A View for a single notification banner. |
| class NotificationView : public views::View { |
| public: |
| explicit NotificationView(const DialogNotification& data) : checkbox_(NULL) { |
| scoped_ptr<views::View> label_view; |
| if (data.HasCheckbox()) { |
| scoped_ptr<views::Checkbox> checkbox( |
| new views::Checkbox(base::string16())); |
| if (!data.interactive()) |
| checkbox->SetState(views::Button::STATE_DISABLED); |
| 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_ = checkbox.get(); |
| label_view.reset(checkbox.release()); |
| } else { |
| scoped_ptr<views::Label> label(new views::Label()); |
| label->SetText(data.display_text()); |
| label->SetMultiLine(true); |
| label->SetHorizontalAlignment(gfx::ALIGN_LEFT); |
| label->SetAutoColorReadabilityEnabled(false); |
| label->SetEnabledColor(data.GetTextColor()); |
| 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())); |
| set_border(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) OVERRIDE { |
| int label_width = width - GetInsets().width(); |
| if (child_count() > 1) { |
| 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()); |
| } |
| |
| private: |
| // The checkbox associated with this notification, or NULL if there is none. |
| views::Checkbox* checkbox_; |
| |
| DISALLOW_COPY_AND_ASSIGN(NotificationView); |
| }; |
| |
| } // namespace |
| |
| // AutofillDialogViews::ErrorBubble -------------------------------------------- |
| |
| AutofillDialogViews::ErrorBubble::ErrorBubble(views::View* anchor, |
| const base::string16& message) |
| : anchor_(anchor), |
| contents_(new ErrorBubbleContents(message)), |
| observer_(this) { |
| widget_ = new views::Widget; |
| views::Widget::InitParams params(views::Widget::InitParams::TYPE_POPUP); |
| params.opacity = views::Widget::InitParams::TRANSLUCENT_WINDOW; |
| views::Widget* anchor_widget = anchor->GetWidget(); |
| DCHECK(anchor_widget); |
| params.parent = anchor_widget->GetNativeView(); |
| |
| widget_->Init(params); |
| widget_->SetContentsView(contents_); |
| UpdatePosition(); |
| observer_.Add(widget_); |
| } |
| |
| AutofillDialogViews::ErrorBubble::~ErrorBubble() { |
| if (widget_) |
| widget_->Close(); |
| } |
| |
| bool AutofillDialogViews::ErrorBubble::IsShowing() { |
| return widget_ && widget_->IsVisible(); |
| } |
| |
| void AutofillDialogViews::ErrorBubble::UpdatePosition() { |
| if (!widget_) |
| return; |
| |
| if (!anchor_->GetVisibleBounds().IsEmpty()) { |
| widget_->SetBounds(GetBoundsForWidget()); |
| widget_->SetVisibilityChangedAnimationsEnabled(true); |
| widget_->Show(); |
| } else { |
| widget_->SetVisibilityChangedAnimationsEnabled(false); |
| widget_->Hide(); |
| } |
| } |
| |
| void AutofillDialogViews::ErrorBubble::OnWidgetClosing(views::Widget* widget) { |
| DCHECK_EQ(widget_, widget); |
| observer_.Remove(widget_); |
| widget_ = NULL; |
| } |
| |
| gfx::Rect AutofillDialogViews::ErrorBubble::GetBoundsForWidget() { |
| gfx::Rect anchor_bounds = anchor_->GetBoundsInScreen(); |
| gfx::Rect bubble_bounds; |
| bubble_bounds.set_size(contents_->GetPreferredSize()); |
| bubble_bounds.set_x(anchor_bounds.right() - |
| (anchor_bounds.width() + bubble_bounds.width()) / 2); |
| const int kErrorBubbleOverlap = 3; |
| bubble_bounds.set_y(anchor_bounds.bottom() - kErrorBubbleOverlap); |
| |
| return bubble_bounds; |
| } |
| |
| // AutofillDialogViews::AccountChooser ----------------------------------------- |
| |
| AutofillDialogViews::AccountChooser::AccountChooser( |
| AutofillDialogViewDelegate* delegate) |
| : image_(new views::ImageView()), |
| label_(new views::Label()), |
| arrow_(new views::ImageView()), |
| link_(new views::Link()), |
| delegate_(delegate) { |
| SetLayoutManager( |
| new views::BoxLayout(views::BoxLayout::kHorizontal, 0, 0, |
| kAroundTextPadding)); |
| AddChildView(image_); |
| AddChildView(label_); |
| |
| arrow_->SetImage(ui::ResourceBundle::GetSharedInstance().GetImageNamed( |
| IDR_MENU_DROPARROW).ToImageSkia()); |
| AddChildView(arrow_); |
| |
| link_->set_listener(this); |
| AddChildView(link_); |
| } |
| |
| AutofillDialogViews::AccountChooser::~AccountChooser() {} |
| |
| void AutofillDialogViews::AccountChooser::Update() { |
| SetVisible(!delegate_->ShouldShowSpinner()); |
| |
| gfx::Image icon = delegate_->AccountChooserImage(); |
| image_->SetImage(icon.AsImageSkia()); |
| label_->SetText(delegate_->AccountChooserText()); |
| |
| bool show_link = !delegate_->MenuModelForAccountChooser(); |
| label_->SetVisible(!show_link); |
| arrow_->SetVisible(!show_link); |
| link_->SetText(delegate_->SignInLinkText()); |
| link_->SetVisible(show_link); |
| |
| menu_runner_.reset(); |
| |
| PreferredSizeChanged(); |
| } |
| |
| bool AutofillDialogViews::AccountChooser::OnMousePressed( |
| const ui::MouseEvent& event) { |
| // Return true so we get the release event. |
| if (delegate_->MenuModelForAccountChooser()) |
| return event.IsOnlyLeftMouseButton(); |
| |
| return false; |
| } |
| |
| void AutofillDialogViews::AccountChooser::OnMouseReleased( |
| const ui::MouseEvent& event) { |
| if (!HitTestPoint(event.location())) |
| return; |
| |
| ui::MenuModel* model = delegate_->MenuModelForAccountChooser(); |
| if (!model) |
| return; |
| |
| menu_runner_.reset(new views::MenuRunner(model)); |
| ignore_result( |
| menu_runner_->RunMenuAt(GetWidget(), |
| NULL, |
| GetBoundsInScreen(), |
| views::MenuItemView::TOPRIGHT, |
| ui::MENU_SOURCE_MOUSE, |
| 0)); |
| } |
| |
| void AutofillDialogViews::AccountChooser::LinkClicked(views::Link* source, |
| int event_flags) { |
| delegate_->SignInLinkClicked(); |
| } |
| |
| // AutofillDialogViews::OverlayView -------------------------------------------- |
| |
| AutofillDialogViews::OverlayView::OverlayView(views::ButtonListener* listener) |
| : image_view_(new views::ImageView()), |
| message_stack_(new views::View()), |
| button_(new views::BlueButton(listener, base::string16())) { |
| set_background(views::Background::CreateSolidBackground(GetNativeTheme()-> |
| GetSystemColor(ui::NativeTheme::kColorId_DialogBackground))); |
| |
| AddChildView(image_view_); |
| |
| AddChildView(message_stack_); |
| message_stack_->SetLayoutManager( |
| new views::BoxLayout(views::BoxLayout::kVertical, 0, 0, |
| kOverlayTextInterlineSpacing)); |
| message_stack_->set_border(views::Border::CreateEmptyBorder( |
| kDialogEdgePadding, kDialogEdgePadding, 0, kDialogEdgePadding)); |
| |
| AddChildView(button_); |
| button_->set_focusable(true); |
| } |
| |
| AutofillDialogViews::OverlayView::~OverlayView() {} |
| |
| int AutofillDialogViews::OverlayView::GetHeightForContentsForWidth(int w) { |
| // In this case, 0 means "no preference". |
| if (!message_stack_->visible()) |
| return 0; |
| |
| return kOverlayImageBottomMargin + |
| views::kButtonVEdgeMarginNew + |
| message_stack_->GetHeightForWidth(w) + |
| image_view_->GetHeightForWidth(w) + |
| (button_->visible() ? button_->GetHeightForWidth(w) + |
| views::kButtonVEdgeMarginNew : 0); |
| } |
| |
| void AutofillDialogViews::OverlayView::SetState( |
| const DialogOverlayState& state, |
| views::ButtonListener* listener) { |
| // Don't update anything if we're still fading out the old state. |
| if (fade_out_) |
| return; |
| |
| if (state.image.IsEmpty()) { |
| SetVisible(false); |
| return; |
| } |
| |
| image_view_->SetImage(state.image.ToImageSkia()); |
| |
| message_stack_->RemoveAllChildViews(true); |
| for (size_t i = 0; i < state.strings.size(); ++i) { |
| views::Label* label = new views::Label(); |
| label->SetAutoColorReadabilityEnabled(false); |
| label->SetMultiLine(true); |
| label->SetText(state.strings[i].text); |
| label->SetFont(state.strings[i].font); |
| label->SetEnabledColor(state.strings[i].text_color); |
| label->SetHorizontalAlignment(state.strings[i].alignment); |
| message_stack_->AddChildView(label); |
| } |
| message_stack_->SetVisible(message_stack_->child_count() > 0); |
| |
| button_->SetVisible(!state.button_text.empty()); |
| if (!state.button_text.empty()) |
| button_->SetText(state.button_text); |
| |
| SetVisible(true); |
| if (parent()) |
| parent()->Layout(); |
| } |
| |
| void AutofillDialogViews::OverlayView::BeginFadeOut() { |
| Parts parts; |
| // For this part of the animation, simply show the splash image. |
| parts.push_back(Part(kSplashDisplayDurationMs, ui::Tween::ZERO)); |
| // For this part of the animation, fade out the splash image. |
| parts.push_back(Part(kSplashFadeOutDurationMs, ui::Tween::EASE_IN)); |
| // For this part of the animation, fade out |this| (fade in the dialog). |
| parts.push_back(Part(kSplashFadeInDialogDurationMs, ui::Tween::EASE_OUT)); |
| fade_out_.reset( |
| new ui::MultiAnimation(parts, |
| ui::MultiAnimation::GetDefaultTimerInterval())); |
| fade_out_->set_delegate(this); |
| fade_out_->set_continuous(false); |
| fade_out_->Start(); |
| } |
| |
| void AutofillDialogViews::OverlayView::AnimationProgressed( |
| const ui::Animation* animation) { |
| DCHECK_EQ(animation, fade_out_.get()); |
| if (fade_out_->current_part_index() != 0) |
| SchedulePaint(); |
| } |
| |
| void AutofillDialogViews::OverlayView::AnimationEnded( |
| const ui::Animation* animation) { |
| DCHECK_EQ(animation, fade_out_.get()); |
| SetVisible(false); |
| fade_out_.reset(); |
| } |
| |
| gfx::Insets AutofillDialogViews::OverlayView::GetInsets() const { |
| return gfx::Insets(12, 12, 12, 12); |
| } |
| |
| void AutofillDialogViews::OverlayView::Layout() { |
| gfx::Rect bounds = ContentBoundsSansBubbleBorder(); |
| if (!message_stack_->visible()) { |
| image_view_->SetBoundsRect(bounds); |
| return; |
| } |
| |
| int y = bounds.bottom() - views::kButtonVEdgeMarginNew; |
| if (button_->visible()) { |
| button_->SizeToPreferredSize(); |
| y -= button_->height(); |
| button_->SetPosition(gfx::Point( |
| bounds.CenterPoint().x() - button_->width() / 2, y)); |
| y -= views::kButtonVEdgeMarginNew; |
| } |
| |
| int message_height = message_stack_->GetHeightForWidth(bounds.width()); |
| y -= message_height; |
| message_stack_->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); |
| |
| // Fade out background (i.e. fade in what's behind |this|). |
| if (fade_out_ && fade_out_->current_part_index() == 2) |
| canvas->SaveLayerAlpha((1 - fade_out_->GetCurrentValue()) * 255); |
| |
| OnPaintBackground(canvas); |
| |
| // Draw the arrow, border, and fill for the bottom area. |
| if (message_stack_->visible()) { |
| const int arrow_half_width = kArrowWidth / 2.0f; |
| SkPath arrow; |
| int y = message_stack_->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(); |
| |
| SkPaint paint; |
| paint.setColor(kShadingColor); |
| paint.setStyle(SkPaint::kFill_Style); |
| canvas->DrawPath(arrow, paint); |
| paint.setColor(kSubtleBorderColor); |
| paint.setStyle(SkPaint::kStroke_Style); |
| canvas->DrawPath(arrow, paint); |
| } |
| |
| PaintChildren(canvas); |
| } |
| |
| void AutofillDialogViews::OverlayView::PaintChildren(gfx::Canvas* canvas) { |
| // Don't draw children. |
| if (fade_out_ && fade_out_->current_part_index() == 2) |
| return; |
| |
| // Fade out children. |
| if (fade_out_ && fade_out_->current_part_index() == 1) |
| canvas->SaveLayerAlpha((1 - fade_out_->GetCurrentValue()) * 255); |
| |
| views::View::PaintChildren(canvas); |
| } |
| |
| 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), |
| checkbox_(NULL) { |
| // Reserve vertical space for the arrow (regardless of whether one exists). |
| // The -1 accounts for the border. |
| set_border(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); |
| checkbox_ = NULL; |
| |
| 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)); |
| if (view->checkbox()) |
| checkbox_ = view->checkbox(); |
| |
| AddChildView(view.release()); |
| } |
| |
| if (checkbox_) |
| checkbox_->set_listener(this); |
| |
| PreferredSizeChanged(); |
| } |
| |
| gfx::Size AutofillDialogViews::NotificationArea::GetPreferredSize() { |
| 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) {} |
| |
| void AutofillDialogViews::NotificationArea::OnPaint(gfx::Canvas* canvas) { |
| views::View::OnPaint(canvas); |
| views::View::PaintChildren(canvas); |
| |
| if (HasArrow()) { |
| DrawArrow( |
| canvas, |
| GetMirroredXInView(width() - arrow_centering_anchor_->width() / 2.0f), |
| notifications_[0].GetBackgroundColor(), |
| notifications_[0].GetBorderColor()); |
| } |
| } |
| |
| void AutofillDialogViews::OnWidgetClosing(views::Widget* widget) { |
| observer_.Remove(widget); |
| } |
| |
| void AutofillDialogViews::OnWidgetBoundsChanged(views::Widget* widget, |
| const gfx::Rect& new_bounds) { |
| ContentsPreferredSizeChanged(); |
| } |
| |
| void AutofillDialogViews::NotificationArea::ButtonPressed( |
| views::Button* sender, const ui::Event& event) { |
| DCHECK_EQ(sender, checkbox_); |
| delegate_->NotificationCheckboxStateChanged(notifications_.front().type(), |
| checkbox_->checked()); |
| } |
| |
| 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); |
| |
| set_border(views::Border::CreateEmptyBorder(kDetailSectionVerticalPadding, |
| kDialogEdgePadding, |
| kDetailSectionVerticalPadding, |
| kDialogEdgePadding)); |
| |
| // TODO(estade): this label should be semi-bold. |
| views::Label* label_view = new views::Label(label); |
| 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); |
| // TODO(estade): do something about this '480'. |
| columns->AddColumn(views::GridLayout::LEADING, |
| views::GridLayout::LEADING, |
| 0, |
| views::GridLayout::FIXED, |
| 480, |
| 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); |
| } |
| |
| void AutofillDialogViews::SectionContainer::OnMouseMoved( |
| const ui::MouseEvent& event) { |
| if (!forward_mouse_events_) |
| return; |
| |
| SetActive(true); |
| } |
| |
| void AutofillDialogViews::SectionContainer::OnMouseEntered( |
| const ui::MouseEvent& event) { |
| if (!forward_mouse_events_) |
| return; |
| |
| SetActive(true); |
| proxy_button_->OnMouseEntered(ProxyEvent(event)); |
| SchedulePaint(); |
| } |
| |
| void AutofillDialogViews::SectionContainer::OnMouseExited( |
| const ui::MouseEvent& event) { |
| if (!forward_mouse_events_) |
| return; |
| |
| SetActive(false); |
| proxy_button_->OnMouseExited(ProxyEvent(event)); |
| SchedulePaint(); |
| } |
| |
| bool AutofillDialogViews::SectionContainer::OnMousePressed( |
| const ui::MouseEvent& event) { |
| if (!forward_mouse_events_) |
| return false; |
| |
| return proxy_button_->OnMousePressed(ProxyEvent(event)); |
| } |
| |
| void AutofillDialogViews::SectionContainer::OnMouseReleased( |
| const ui::MouseEvent& event) { |
| if (!forward_mouse_events_) |
| return; |
| |
| proxy_button_->OnMouseReleased(ProxyEvent(event)); |
| } |
| |
| // static |
| ui::MouseEvent AutofillDialogViews::SectionContainer::ProxyEvent( |
| const ui::MouseEvent& event) { |
| ui::MouseEvent event_copy = event; |
| event_copy.set_location(gfx::Point()); |
| return event_copy; |
| } |
| |
| // AutofilDialogViews::SuggestionView ------------------------------------------ |
| |
| AutofillDialogViews::SuggestionView::SuggestionView( |
| const base::string16& edit_label, |
| AutofillDialogViews* autofill_dialog) |
| : label_(new views::Label()), |
| label_line_2_(new views::Label()), |
| icon_(new views::ImageView()), |
| label_container_(new SectionRowView()), |
| decorated_( |
| new DecoratedTextfield(base::string16(), |
| base::string16(), |
| autofill_dialog)) { |
| // TODO(estade): Make this the correct color. |
| set_border( |
| views::Border::CreateSolidSidedBorder(1, 0, 0, 0, SK_ColorLTGRAY)); |
| |
| // Label and icon. |
| label_->SetHorizontalAlignment(gfx::ALIGN_LEFT); |
| label_container_->AddChildView(icon_); |
| label_container_->AddChildView(label_); |
| label_container_->AddChildView(decorated_); |
| decorated_->SetVisible(false); |
| // TODO(estade): get the sizing and spacing right on this textfield. |
| decorated_->set_default_width_in_chars(10); |
| AddChildView(label_container_); |
| |
| label_line_2_->SetHorizontalAlignment(gfx::ALIGN_LEFT); |
| label_line_2_->SetVisible(false); |
| label_line_2_->SetMultiLine(true); |
| AddChildView(label_line_2_); |
| |
| // TODO(estade): do something about this '2'. |
| SetLayoutManager(new views::BoxLayout(views::BoxLayout::kVertical, 0, 2, 0)); |
| } |
| |
| AutofillDialogViews::SuggestionView::~SuggestionView() {} |
| |
| void AutofillDialogViews::SuggestionView::SetSuggestionText( |
| const base::string16& text, |
| gfx::Font::FontStyle text_style) { |
| label_->SetFont(ui::ResourceBundle::GetSharedInstance().GetFont( |
| ui::ResourceBundle::BaseFont).DeriveFont(0, text_style)); |
| |
| // TODO(estade): does this localize well? |
| base::string16 line_return(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::SetSuggestionIcon( |
| const gfx::Image& image) { |
| icon_->SetVisible(!image.IsEmpty()); |
| icon_->SetImage(image.AsImageSkia()); |
| } |
| |
| void AutofillDialogViews::SuggestionView::ShowTextfield( |
| const base::string16& placeholder_text, |
| const gfx::Image& icon) { |
| decorated_->set_placeholder_text(placeholder_text); |
| decorated_->SetIcon(icon); |
| decorated_->SetVisible(true); |
| // The textfield will increase the height of the first row and cause the |
| // label to be aligned properly, so the border is not necessary. |
| label_->set_border(NULL); |
| } |
| |
| // AutofillDialogViews::AutocheckoutStepsArea --------------------------------- |
| |
| AutofillDialogViews::AutocheckoutStepsArea::AutocheckoutStepsArea() { |
| SetLayoutManager(new views::BoxLayout(views::BoxLayout::kVertical, |
| kAutocheckoutStepsAreaPadding, |
| 0, |
| kAutocheckoutStepInset)); |
| } |
| |
| void AutofillDialogViews::AutocheckoutStepsArea::SetSteps( |
| const std::vector<DialogAutocheckoutStep>& steps) { |
| RemoveAllChildViews(true); |
| for (size_t i = 0; i < steps.size(); ++i) { |
| const DialogAutocheckoutStep& step = steps[i]; |
| AutocheckoutStepProgressView* progressView = |
| new AutocheckoutStepProgressView(step.GetDisplayText(), |
| step.GetTextFont(), |
| step.GetTextColor(), |
| step.IsIconVisible()); |
| |
| AddChildView(progressView); |
| } |
| |
| PreferredSizeChanged(); |
| } |
| |
| // AutofillDialogViews::AutocheckoutProgressBar |
| |
| AutofillDialogViews::AutocheckoutProgressBar::AutocheckoutProgressBar() {} |
| AutofillDialogViews::AutocheckoutProgressBar::~AutocheckoutProgressBar() {} |
| |
| gfx::Size AutofillDialogViews::AutocheckoutProgressBar::GetPreferredSize() { |
| return gfx::Size(kAutocheckoutProgressBarWidth, |
| kAutocheckoutProgressBarHeight); |
| } |
| |
| // AutofillDialogView ---------------------------------------------------------- |
| |
| // static |
| AutofillDialogView* AutofillDialogView::Create( |
| AutofillDialogViewDelegate* delegate) { |
| return new AutofillDialogViews(delegate); |
| } |
| |
| // AutofillDialogViews --------------------------------------------------------- |
| |
| AutofillDialogViews::AutofillDialogViews(AutofillDialogViewDelegate* delegate) |
| : delegate_(delegate), |
| window_(NULL), |
| notification_area_(NULL), |
| account_chooser_(NULL), |
| sign_in_webview_(NULL), |
| scrollable_area_(NULL), |
| details_container_(NULL), |
| loading_shield_(NULL), |
| overlay_view_(NULL), |
| button_strip_extra_view_(NULL), |
| save_in_chrome_checkbox_(NULL), |
| button_strip_image_(NULL), |
| autocheckout_steps_area_(NULL), |
| autocheckout_progress_bar_view_(NULL), |
| autocheckout_progress_bar_(NULL), |
| footnote_view_(NULL), |
| legal_document_view_(NULL), |
| focus_manager_(NULL), |
| observer_(this) { |
| DCHECK(delegate); |
| detail_groups_.insert(std::make_pair(SECTION_EMAIL, |
| DetailsGroup(SECTION_EMAIL))); |
| 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() { |
| DCHECK(!window_); |
| } |
| |
| void AutofillDialogViews::Show() { |
| InitChildViews(); |
| UpdateAccountChooser(); |
| UpdateNotificationArea(); |
| UpdateButtonStripExtraView(); |
| |
| // Ownership of |contents_| is handed off by this call. The widget will take |
| // care of deleting itself after calling DeleteDelegate(). |
| WebContentsModalDialogManager* web_contents_modal_dialog_manager = |
| WebContentsModalDialogManager::FromWebContents( |
| delegate_->web_contents()); |
| window_ = CreateWebContentsModalDialogViews( |
| this, |
| delegate_->web_contents()->GetView()->GetNativeView(), |
| web_contents_modal_dialog_manager->delegate()-> |
| GetWebContentsModalDialogHost()); |
| web_contents_modal_dialog_manager->ShowDialog(window_->GetNativeView()); |
| focus_manager_ = window_->GetFocusManager(); |
| focus_manager_->AddFocusChangeListener(this); |
| |
| // Listen for size changes on the browser. |
| views::Widget* browser_widget = |
| views::Widget::GetTopLevelWidgetForNativeView( |
| delegate_->web_contents()->GetView()->GetNativeView()); |
| observer_.Add(browser_widget); |
| |
| gfx::Image splash_image = delegate_->SplashPageImage(); |
| if (!splash_image.IsEmpty()) { |
| DialogOverlayState state; |
| state.image = splash_image; |
| overlay_view_->SetState(state, NULL); |
| overlay_view_->BeginFadeOut(); |
| } |
| } |
| |
| void AutofillDialogViews::Hide() { |
| if (window_) |
| window_->Close(); |
| } |
| |
| void AutofillDialogViews::UpdateAccountChooser() { |
| account_chooser_->Update(); |
| // TODO(estade): replace this with a better loading image/animation. |
| // See http://crbug.com/230932 |
| base::string16 new_loading_message = (delegate_->ShouldShowSpinner() ? |
| ASCIIToUTF16("Loading...") : base::string16()); |
| if (new_loading_message != loading_shield_->text()) { |
| loading_shield_->SetText(new_loading_message); |
| loading_shield_->SetVisible(!new_loading_message.empty()); |
| Layout(); |
| } |
| |
| // 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<ui::Range>& link_ranges = |
| delegate_->LegalDocumentLinks(); |
| for (size_t i = 0; i < link_ranges.size(); ++i) { |
| legal_document_view_->AddStyleRange( |
| link_ranges[i], |
| views::StyledLabel::RangeStyleInfo::CreateForLink()); |
| } |
| } |
| |
| footnote_view_->SetVisible(!text.empty()); |
| ContentsPreferredSizeChanged(); |
| } |
| } |
| |
| void AutofillDialogViews::UpdateAutocheckoutStepsArea() { |
| autocheckout_steps_area_->SetSteps(delegate_->CurrentAutocheckoutSteps()); |
| ContentsPreferredSizeChanged(); |
| } |
| |
| void AutofillDialogViews::UpdateButtonStrip() { |
| button_strip_extra_view_->SetVisible( |
| GetDialogButtons() != ui::DIALOG_BUTTON_NONE); |
| UpdateButtonStripExtraView(); |
| GetDialogClientView()->UpdateDialogButtons(); |
| |
| overlay_view_->SetState(delegate_->GetDialogOverlay(), this); |
| |
| ContentsPreferredSizeChanged(); |
| } |
| |
| void AutofillDialogViews::UpdateDetailArea() { |
| scrollable_area_->SetVisible(delegate_->ShouldShowDetailArea()); |
| 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::FillSection(DialogSection section, |
| const DetailInput& originating_input) { |
| DetailsGroup* group = GroupForSection(section); |
| // Make sure to overwrite the originating input. |
| TextfieldMap::iterator text_mapping = |
| group->textfields.find(&originating_input); |
| 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 == SECTION_CC || section == SECTION_CC_BILLING) && |
| AutofillType(originating_input.type).group() == CREDIT_CARD) { |
| for (ComboboxMap::const_iterator it = group->comboboxes.begin(); |
| it != group->comboboxes.end(); ++it) { |
| if (AutofillType(it->first->type).group() == CREDIT_CARD) |
| it->second->SetSelectedIndex(it->second->model()->GetDefaultIndex()); |
| } |
| } |
| |
| UpdateSectionImpl(section, false); |
| } |
| |
| void AutofillDialogViews::GetUserInput(DialogSection section, |
| DetailOutputMap* 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->text())); |
| } |
| 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() { |
| DialogSection billing_section = delegate_->SectionIsActive(SECTION_CC) ? |
| SECTION_CC : SECTION_CC_BILLING; |
| return GroupForSection(billing_section)->suggested_info-> |
| decorated_textfield()->text(); |
| } |
| |
| bool AutofillDialogViews::SaveDetailsLocally() { |
| DCHECK(save_in_chrome_checkbox_->visible()); |
| return save_in_chrome_checkbox_->checked(); |
| } |
| |
| const content::NavigationController* AutofillDialogViews::ShowSignIn() { |
| // 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_webview_->LoadInitialURL(wallet::GetSignInUrl()); |
| |
| sign_in_webview_->SetVisible(true); |
| sign_in_webview_->RequestFocus(); |
| UpdateButtonStrip(); |
| ContentsPreferredSizeChanged(); |
| return &sign_in_webview_->web_contents()->GetController(); |
| } |
| |
| void AutofillDialogViews::HideSignIn() { |
| sign_in_webview_->SetVisible(false); |
| UpdateButtonStrip(); |
| ContentsPreferredSizeChanged(); |
| } |
| |
| void AutofillDialogViews::UpdateProgressBar(double value) { |
| autocheckout_progress_bar_->SetValue(value); |
| } |
| |
| void AutofillDialogViews::ModelChanged() { |
| menu_runner_.reset(); |
| |
| for (DetailGroupMap::const_iterator iter = detail_groups_.begin(); |
| iter != detail_groups_.end(); ++iter) { |
| UpdateDetailsGroupState(iter->second); |
| } |
| } |
| |
| TestableAutofillDialogView* AutofillDialogViews::GetTestableView() { |
| return this; |
| } |
| |
| void AutofillDialogViews::OnSignInResize(const gfx::Size& pref_size) { |
| sign_in_webview_->SetPreferredSize(pref_size); |
| ContentsPreferredSizeChanged(); |
| } |
| |
| void AutofillDialogViews::SubmitForTesting() { |
| Accept(); |
| } |
| |
| void AutofillDialogViews::CancelForTesting() { |
| GetDialogClientView()->CancelWindow(); |
| } |
| |
| base::string16 AutofillDialogViews::GetTextContentsOfInput( |
| const DetailInput& input) { |
| views::Textfield* textfield = TextfieldForInput(input); |
| if (textfield) |
| return textfield->text(); |
| |
| views::Combobox* combobox = ComboboxForInput(input); |
| if (combobox) |
| return combobox->model()->GetItemAt(combobox->selected_index()); |
| |
| NOTREACHED(); |
| return base::string16(); |
| } |
| |
| void AutofillDialogViews::SetTextContentsOfInput( |
| const DetailInput& input, |
| const base::string16& contents) { |
| views::Textfield* textfield = TextfieldForInput(input); |
| if (textfield) { |
| TextfieldForInput(input)->SetText(contents); |
| return; |
| } |
| |
| views::Combobox* combobox = ComboboxForInput(input); |
| if (combobox) { |
| for (int i = 0; i < combobox->model()->GetItemCount(); ++i) { |
| if (contents == combobox->model()->GetItemAt(i)) { |
| combobox->SetSelectedIndex(i); |
| return; |
| } |
| } |
| // If we don't find a match, return the combobox to its default state. |
| combobox->SetSelectedIndex(combobox->model()->GetDefaultIndex()); |
| return; |
| } |
| |
| NOTREACHED(); |
| } |
| |
| void AutofillDialogViews::SetTextContentsOfSuggestionInput( |
| DialogSection section, |
| const base::string16& text) { |
| GroupForSection(section)->suggested_info->decorated_textfield()-> |
| SetText(text); |
| } |
| |
| void AutofillDialogViews::ActivateInput(const DetailInput& input) { |
| TextfieldEditedOrActivated(TextfieldForInput(input), false); |
| } |
| |
| gfx::Size AutofillDialogViews::GetSize() const { |
| return GetWidget() ? GetWidget()->GetRootView()->size() : gfx::Size(); |
| } |
| |
| gfx::Size AutofillDialogViews::GetPreferredSize() { |
| gfx::Insets insets = GetInsets(); |
| gfx::Size scroll_size = scrollable_area_->contents()->GetPreferredSize(); |
| int width = scroll_size.width() + insets.width(); |
| |
| if (sign_in_webview_->visible()) { |
| gfx::Size size = static_cast<views::View*>(sign_in_webview_)-> |
| GetPreferredSize(); |
| return gfx::Size(width, size.height() + insets.height()); |
| } |
| |
| int base_height = insets.height(); |
| int notification_height = notification_area_-> |
| GetHeightForWidth(scroll_size.width()); |
| if (notification_height > 0) |
| base_height += notification_height + views::kRelatedControlVerticalSpacing; |
| |
| int steps_height = autocheckout_steps_area_-> |
| GetHeightForWidth(scroll_size.width()); |
| if (steps_height > 0) |
| base_height += steps_height + views::kRelatedControlVerticalSpacing; |
| |
| gfx::Size preferred_size; |
| // When the scroll area isn't visible, it still sets the width but doesn't |
| // factor into height. |
| if (!scrollable_area_->visible()) { |
| preferred_size.SetSize(width, base_height); |
| } else { |
| // Show as much of the scroll view as is possible without going past the |
| // bottom of the browser window. |
| views::Widget* widget = |
| views::Widget::GetTopLevelWidgetForNativeView( |
| delegate_->web_contents()->GetView()->GetNativeView()); |
| int browser_window_height = |
| widget ? widget->GetContentsView()->bounds().height() : INT_MAX; |
| const int kWindowDecorationHeight = 200; |
| int height = base_height + std::min( |
| scroll_size.height(), |
| std::max(kMinimumContentsHeight, |
| browser_window_height - base_height - |
| kWindowDecorationHeight)); |
| preferred_size.SetSize(width, height); |
| } |
| |
| if (!overlay_view_->visible()) |
| return preferred_size; |
| |
| int height_of_overlay = |
| overlay_view_->GetHeightForContentsForWidth(preferred_size.width()); |
| if (height_of_overlay > 0) |
| preferred_size.set_height(height_of_overlay); |
| |
| return preferred_size; |
| } |
| |
| void AutofillDialogViews::Layout() { |
| gfx::Rect content_bounds = GetContentsBounds(); |
| if (sign_in_webview_->visible()) { |
| sign_in_webview_->SetBoundsRect(content_bounds); |
| return; |
| } |
| |
| const int x = content_bounds.x(); |
| const int y = content_bounds.y(); |
| const int w = content_bounds.width(); |
| // Layout notification area at top of dialog. |
| int notification_height = notification_area_->GetHeightForWidth(w); |
| notification_area_->SetBounds(x, y, w, notification_height); |
| |
| // Layout Autocheckout steps at bottom of dialog. |
| int steps_height = autocheckout_steps_area_->GetHeightForWidth(w); |
| autocheckout_steps_area_->SetBounds(x, content_bounds.bottom() - steps_height, |
| w, steps_height); |
| |
| // The rest (the |scrollable_area_|) takes up whatever's left. |
| if (scrollable_area_->visible()) { |
| int scroll_y = y; |
| if (notification_height > 0) |
| scroll_y += notification_height + views::kRelatedControlVerticalSpacing; |
| |
| int scroll_bottom = content_bounds.bottom(); |
| if (steps_height > 0) |
| scroll_bottom -= steps_height + views::kRelatedControlVerticalSpacing; |
| |
| scrollable_area_->contents()->SizeToPreferredSize(); |
| scrollable_area_->SetBounds(x, scroll_y, w, scroll_bottom - scroll_y); |
| } |
| |
| if (loading_shield_->visible()) |
| loading_shield_->SetBoundsRect(bounds()); |
| |
| if (error_bubble_) |
| error_bubble_->UpdatePosition(); |
| } |
| |
| void AutofillDialogViews::OnBoundsChanged(const gfx::Rect& previous_bounds) { |
| sign_in_delegate_->SetMinWidth(GetContentsBounds().width()); |
| } |
| |
| base::string16 AutofillDialogViews::GetWindowTitle() const { |
| return delegate_->DialogTitle(); |
| } |
| |
| void AutofillDialogViews::WindowClosing() { |
| focus_manager_->RemoveFocusChangeListener(this); |
| } |
| |
| void AutofillDialogViews::DeleteDelegate() { |
| window_ = NULL; |
| // |this| belongs to the controller (|delegate_|). |
| delegate_->ViewClosed(); |
| } |
| |
| int AutofillDialogViews::GetDialogButtons() const { |
| if (sign_in_webview_->visible()) |
| return ui::DIALOG_BUTTON_NONE; |
| |
| return delegate_->GetDialogButtons(); |
| } |
| |
| 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::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_->set_border( |
| 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); |
| views::StyledLabel::RangeStyleInfo default_style; |
| default_style.color = kGreyTextColor; |
| legal_document_view_->SetDefaultStyle(default_style); |
| |
| 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(); |
| |
| if (!validity_map_.empty()) |
| validity_map_.begin()->first->RequestFocus(); |
| return false; |
| } |
| |
| // TODO(wittman): Remove this override once we move to the new style frame view |
| // on all dialogs. |
| views::NonClientFrameView* AutofillDialogViews::CreateNonClientFrameView( |
| views::Widget* widget) { |
| return CreateConstrainedStyleNonClientFrameView( |
| widget, |
| delegate_->web_contents()->GetBrowserContext()); |
| } |
| |
| void AutofillDialogViews::ButtonPressed(views::Button* sender, |
| const ui::Event& event) { |
| if (sender->GetAncestorWithClassName(kOverlayViewClassName)) { |
| delegate_->OverlayButtonPressed(); |
| return; |
| } |
| |
| // TODO(estade): Should the menu be shown on mouse down? |
| DetailsGroup* group = NULL; |
| for (DetailGroupMap::iterator iter = detail_groups_.begin(); |
| iter != detail_groups_.end(); ++iter) { |
| if (sender == 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))); |
| |
| group->container->SetActive(true); |
| views::Button::ButtonState state = group->suggested_button->state(); |
| group->suggested_button->SetState(views::Button::STATE_PRESSED); |
| // Ignore the result since we don't need to handle a deleted menu specially. |
| gfx::Rect bounds = group->suggested_button->GetBoundsInScreen(); |
| bounds.Inset(group->suggested_button->GetInsets()); |
| ignore_result( |
| menu_runner_->RunMenuAt(sender->GetWidget(), |
| NULL, |
| bounds, |
| views::MenuItemView::TOPRIGHT, |
| ui::GetMenuSourceTypeForEvent(event), |
| 0)); |
| group->container->SetActive(false); |
| group->suggested_button->SetState(state); |
| } |
| |
| void AutofillDialogViews::ContentsChanged(views::Textfield* sender, |
| const base::string16& new_contents) { |
| TextfieldEditedOrActivated(sender, true); |
| } |
| |
| bool AutofillDialogViews::HandleKeyEvent(views::Textfield* sender, |
| const ui::KeyEvent& key_event) { |
| scoped_ptr<ui::KeyEvent> copy(key_event.Copy()); |
| #if defined(OS_WIN) && !defined(USE_AURA) |
| content::NativeWebKeyboardEvent event(copy->native_event()); |
| #else |
| content::NativeWebKeyboardEvent event(copy.get()); |
| #endif |
| return delegate_->HandleKeyPressEventInInput(event); |
| } |
| |
| bool AutofillDialogViews::HandleMouseEvent(views::Textfield* sender, |
| const ui::MouseEvent& mouse_event) { |
| if (mouse_event.IsLeftMouseButton() && sender->HasFocus()) { |
| TextfieldEditedOrActivated(sender, 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(); |
| error_bubble_.reset(); |
| } |
| |
| 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) |
| ShowErrorBubbleForViewIfNecessary(focused_now); |
| } |
| |
| void AutofillDialogViews::OnSelectedIndexChanged(views::Combobox* combobox) { |
| DetailsGroup* group = GroupForView(combobox); |
| ValidateGroup(*group, VALIDATE_EDIT); |
| } |
| |
| void AutofillDialogViews::StyledLabelLinkClicked(const ui::Range& range, |
| int event_flags) { |
| delegate_->LegalDocumentLinkClicked(range); |
| } |
| |
| 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_ = |
| new views::Checkbox(delegate_->SaveLocallyText()); |
| save_in_chrome_checkbox_->SetChecked(true); |
| button_strip_extra_view_->AddChildView(save_in_chrome_checkbox_); |
| |
| button_strip_image_ = new views::ImageView(); |
| button_strip_extra_view_->AddChildView(button_strip_image_); |
| |
| autocheckout_progress_bar_view_ = new views::View(); |
| views::GridLayout* progress_bar_layout = |
| new views::GridLayout(autocheckout_progress_bar_view_); |
| autocheckout_progress_bar_view_->SetLayoutManager(progress_bar_layout); |
| const int kColumnSetId = 0; |
| views::ColumnSet* columns = progress_bar_layout->AddColumnSet(kColumnSetId); |
| columns->AddColumn(views::GridLayout::LEADING, |
| views::GridLayout::CENTER, |
| 0, |
| views::GridLayout::USE_PREF, |
| 0, |
| 0); |
| progress_bar_layout->StartRow(1.0, kColumnSetId); |
| |
| autocheckout_progress_bar_ = new AutocheckoutProgressBar(); |
| progress_bar_layout->AddView(autocheckout_progress_bar_); |
| button_strip_extra_view_->AddChildView(autocheckout_progress_bar_view_); |
| |
| 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_); |
| |
| autocheckout_steps_area_ = new AutocheckoutStepsArea(); |
| AddChildView(autocheckout_steps_area_); |
| |
| loading_shield_ = new views::Label(); |
| loading_shield_->SetVisible(false); |
| loading_shield_->set_background(views::Background::CreateSolidBackground( |
| GetNativeTheme()->GetSystemColor( |
| ui::NativeTheme::kColorId_DialogBackground))); |
| loading_shield_->SetFont(ui::ResourceBundle::GetSharedInstance().GetFont( |
| ui::ResourceBundle::BaseFont).DeriveFont(15)); |
| AddChildView(loading_shield_); |
| |
| sign_in_webview_ = new views::WebView(delegate_->profile()); |
| sign_in_webview_->SetVisible(false); |
| AddChildView(sign_in_webview_); |
| sign_in_delegate_.reset( |
| new AutofillDialogSignInDelegate(this, |
| sign_in_webview_->GetWebContents())); |
| |
| overlay_view_ = new OverlayView(this); |
| 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)); |
| |
| views::View* manual_inputs = InitInputsView(section); |
| info_view->AddChildView(manual_inputs); |
| SuggestionView* suggested_info = |
| new SuggestionView(delegate_->EditSuggestionText(), this); |
| info_view->AddChildView(suggested_info); |
| |
| // TODO(estade): It might be slightly more OO if this button were created |
| // and listened to by the section container. |
| views::ImageButton* menu_button = new views::ImageButton(this); |
| ui::ResourceBundle& rb = ui::ResourceBundle::GetSharedInstance(); |
| menu_button->SetImage(views::Button::STATE_NORMAL, |
| rb.GetImageSkiaNamed(IDR_AUTOFILL_DIALOG_MENU_BUTTON)); |
| menu_button->SetImage(views::Button::STATE_PRESSED, |
| rb.GetImageSkiaNamed(IDR_AUTOFILL_DIALOG_MENU_BUTTON_P)); |
| menu_button->SetImage(views::Button::STATE_HOVERED, |
| rb.GetImageSkiaNamed(IDR_AUTOFILL_DIALOG_MENU_BUTTON_H)); |
| menu_button->SetImage(views::Button::STATE_DISABLED, |
| rb.GetImageSkiaNamed(IDR_AUTOFILL_DIALOG_MENU_BUTTON_D)); |
| menu_button->set_border(views::Border::CreateEmptyBorder( |
| kMenuButtonTopInset, |
| kDialogEdgePadding, |
| kMenuButtonBottomInset, |
| 0)); |
| |
| DetailsGroup* group = GroupForSection(section); |
| group->suggested_button = menu_button; |
| group->manual_input = manual_inputs; |
| group->suggested_info = suggested_info; |
| return info_view; |
| } |
| |
| // TODO(estade): we should be using Chrome-style constrained window padding |
| // values. |
| views::View* AutofillDialogViews::InitInputsView(DialogSection section) { |
| const DetailInputs& inputs = delegate_->RequestedFieldsForSection(section); |
| TextfieldMap* textfields = &GroupForSection(section)->textfields; |
| ComboboxMap* comboboxes = &GroupForSection(section)->comboboxes; |
| |
| views::View* view = new views::View(); |
| views::GridLayout* layout = new views::GridLayout(view); |
| view->SetLayoutManager(layout); |
| |
| 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, combobox)); |
| |
| for (int i = 0; i < input_model->GetItemCount(); ++i) { |
| if (input.initial_value == input_model->GetItemAt(i)) { |
| combobox->SetSelectedIndex(i); |
| break; |
| } |
| } |
| |
| view_to_add.reset(combobox); |
| } else { |
| DecoratedTextfield* field = new DecoratedTextfield( |
| input.initial_value, |
| l10n_util::GetStringUTF16(input.placeholder_text_rid), |
| this); |
| |
| gfx::Image icon = |
| delegate_->IconForField(input.type, input.initial_value); |
| field->SetIcon(icon); |
| |
| textfields->insert(std::make_pair(&input, field)); |
| view_to_add.reset(field); |
| } |
| |
| int kColumnSetId = input.row_id; |
| if (kColumnSetId < 0) { |
| other_owned_views_.push_back(view_to_add.release()); |
| continue; |
| } |
| |
| views::ColumnSet* column_set = layout->GetColumnSet(kColumnSetId); |
| if (!column_set) { |
| // Create a new column set and row. |
| column_set = layout->AddColumnSet(kColumnSetId); |
| if (it != inputs.begin()) |
| layout->AddPaddingRow(0, views::kRelatedControlVerticalSpacing); |
| layout->StartRow(0, kColumnSetId); |
| } 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); |
| } |
| |
| return view; |
| } |
| |
| void AutofillDialogViews::UpdateSectionImpl( |
| DialogSection section, |
| bool clobber_inputs) { |
| const DetailInputs& updated_inputs = |
| delegate_->RequestedFieldsForSection(section); |
| DetailsGroup* group = GroupForSection(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); |
| |
| if (text_mapping != group->textfields.end()) { |
| DecoratedTextfield* decorated = text_mapping->second; |
| decorated->SetEnabled(input.editable); |
| if (decorated->text().empty() || clobber_inputs) { |
| decorated->SetText(iter->initial_value); |
| decorated->SetIcon( |
| delegate_->IconForField(input.type, decorated->text())); |
| } |
| } |
| |
| ComboboxMap::iterator combo_mapping = group->comboboxes.find(&input); |
| if (combo_mapping != group->comboboxes.end()) { |
| views::Combobox* combobox = combo_mapping->second; |
| combobox->SetEnabled(input.editable); |
| if (combobox->selected_index() == combobox->model()->GetDefaultIndex() || |
| clobber_inputs) { |
| for (int i = 0; i < combobox->model()->GetItemCount(); ++i) { |
| if (input.initial_value == combobox->model()->GetItemAt(i)) { |
| combobox->SetSelectedIndex(i); |
| break; |
| } |
| } |
| } |
| } |
| } |
| |
| UpdateDetailsGroupState(*group); |
| } |
| |
| void AutofillDialogViews::UpdateDetailsGroupState(const DetailsGroup& group) { |
| const SuggestionState& suggestion_state = |
| delegate_->SuggestionStateForSection(group.section); |
| bool show_suggestions = !suggestion_state.text.empty(); |
| group.suggested_info->SetVisible(show_suggestions); |
| group.suggested_info->SetSuggestionText(suggestion_state.text, |
| suggestion_state.text_style); |
| group.suggested_info->SetSuggestionIcon(suggestion_state.icon); |
| |
| if (!suggestion_state.extra_text.empty()) { |
| group.suggested_info->ShowTextfield( |
| suggestion_state.extra_text, |
| suggestion_state.extra_icon); |
| } |
| |
| group.manual_input->SetVisible(!show_suggestions); |
| |
| 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 && show_suggestions); |
| group.container->SetVisible(delegate_->SectionIsActive(group.section)); |
| if (group.container->visible()) |
| ValidateGroup(group, VALIDATE_EDIT); |
| } |
| |
| ContentsPreferredSizeChanged(); |
| } |
| |
| 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() == input) { |
| validity_map_.erase(input); |
| error_bubble_.reset(); |
| } |
| } |
| } |
| |
| void AutofillDialogViews::ShowErrorBubbleForViewIfNecessary(views::View* view) { |
| if (!view->GetWidget()) |
| return; |
| |
| if (error_bubble_ && error_bubble_->anchor() == view) |
| return; |
| |
| std::map<views::View*, base::string16>::iterator error_message = |
| validity_map_.find(view); |
| if (error_message != validity_map_.end()) { |
| view->ScrollRectToVisible(view->GetLocalBounds()); |
| error_bubble_.reset(new ErrorBubble(view, error_message->second)); |
| } |
| } |
| |
| void AutofillDialogViews::MarkInputsInvalid(DialogSection section, |
| const ValidityData& validity_data) { |
| DetailsGroup* group = GroupForSection(section); |
| DCHECK(group->container->visible()); |
| |
| typedef std::map<ServerFieldType, |
| base::Callback<void(const base::string16&)> > FieldMap; |
| FieldMap field_map; |
| |
| if (group->manual_input->visible()) { |
| for (TextfieldMap::const_iterator iter = group->textfields.begin(); |
| iter != group->textfields.end(); ++iter) { |
| field_map[iter->first->type] = base::Bind( |
| &AutofillDialogViews::SetValidityForInput<DecoratedTextfield>, |
| base::Unretained(this), |
| iter->second); |
| } |
| for (ComboboxMap::const_iterator iter = group->comboboxes.begin(); |
| iter != group->comboboxes.end(); ++iter) { |
| field_map[iter->first->type] = base::Bind( |
| &AutofillDialogViews::SetValidityForInput<views::Combobox>, |
| base::Unretained(this), |
| iter->second); |
| } |
| } else { |
| // Purge invisible views from |validity_map_|. |
| std::map<views::View*, base::string16>::iterator it; |
| for (it = validity_map_.begin(); it != validity_map_.end();) { |
| DCHECK(GroupForView(it->first)); |
| if (GroupForView(it->first) == group) |
| validity_map_.erase(it++); |
| else |
| ++it; |
| } |
| |
| if (section == SECTION_CC) { |
| // Special case CVC as it's not part of |group->manual_input|. |
| field_map[CREDIT_CARD_VERIFICATION_CODE] = base::Bind( |
| &AutofillDialogViews::SetValidityForInput<DecoratedTextfield>, |
| base::Unretained(this), |
| group->suggested_info->decorated_textfield()); |
| } |
| } |
| |
| // Flag invalid fields, removing them from |field_map|. |
| for (ValidityData::const_iterator iter = validity_data.begin(); |
| iter != validity_data.end(); ++iter) { |
| const base::string16& message = iter->second; |
| field_map[iter->first].Run(message); |
| field_map.erase(iter->first); |
| } |
| |
| // The remaining fields in |field_map| are valid. Mark them as such. |
| for (FieldMap::iterator iter = field_map.begin(); iter != field_map.end(); |
| ++iter) { |
| iter->second.Run(base::string16()); |
| } |
| } |
| |
| bool AutofillDialogViews::ValidateGroup(const DetailsGroup& group, |
| ValidationType validation_type) { |
| DCHECK(group.container->visible()); |
| |
| scoped_ptr<DetailInput> cvc_input; |
| DetailOutputMap detail_outputs; |
| |
| if (group.manual_input->visible()) { |
| for (TextfieldMap::const_iterator iter = group.textfields.begin(); |
| iter != group.textfields.end(); ++iter) { |
| if (!iter->first->editable) |
| continue; |
| |
| detail_outputs[iter->first] = iter->second->text(); |
| } |
| for (ComboboxMap::const_iterator iter = group.comboboxes.begin(); |
| iter != group.comboboxes.end(); ++iter) { |
| if (!iter->first->editable) |
| continue; |
| |
| views::Combobox* combobox = iter->second; |
| base::string16 item = |
| combobox->model()->GetItemAt(combobox->selected_index()); |
| detail_outputs[iter->first] = item; |
| } |
| } else if (group.section == SECTION_CC) { |
| DecoratedTextfield* decorated_cvc = |
| group.suggested_info->decorated_textfield(); |
| cvc_input.reset(new DetailInput); |
| cvc_input->type = CREDIT_CARD_VERIFICATION_CODE; |
| detail_outputs[cvc_input.get()] = decorated_cvc->text(); |
| } |
| |
| ValidityData invalid_inputs = delegate_->InputsAreValid( |
| group.section, detail_outputs, validation_type); |
| MarkInputsInvalid(group.section, invalid_inputs); |
| |
| return invalid_inputs.empty(); |
| } |
| |
| 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::TextfieldEditedOrActivated( |
| views::Textfield* textfield, |
| bool was_edit) { |
| DetailsGroup* group = GroupForView(textfield); |
| DCHECK(group); |
| |
| // Figure out the ServerFieldType this textfield represents. |
| ServerFieldType type = UNKNOWN_TYPE; |
| DecoratedTextfield* decorated = NULL; |
| |
| // Look for the input in the manual inputs. |
| for (TextfieldMap::const_iterator iter = group->textfields.begin(); |
| iter != group->textfields.end(); |
| ++iter) { |
| decorated = iter->second; |
| if (decorated == textfield) { |
| delegate_->UserEditedOrActivatedInput(group->section, |
| iter->first, |
| GetWidget()->GetNativeView(), |
| textfield->GetBoundsInScreen(), |
| textfield->text(), |
| was_edit); |
| type = iter->first->type; |
| break; |
| } |
| } |
| |
| if (textfield == group->suggested_info->decorated_textfield()) { |
| decorated = group->suggested_info->decorated_textfield(); |
| type = CREDIT_CARD_VERIFICATION_CODE; |
| } |
| DCHECK_NE(UNKNOWN_TYPE, type); |
| |
| // If the field is marked as 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 (decorated->invalid() && was_edit) { |
| SetValidityForInput<DecoratedTextfield>( |
| decorated, |
| delegate_->InputValidityMessage(group->section, type, |
| textfield->text())); |
| |
| // If the field transitioned from invalid to valid, re-validate the group, |
| // since inter-field checks become meaningful with valid fields. |
| if (!decorated->invalid()) |
| ValidateGroup(*group, VALIDATE_EDIT); |
| } |
| |
| gfx::Image icon = delegate_->IconForField(type, textfield->text()); |
| decorated->SetIcon(icon); |
| } |
| |
| void AutofillDialogViews::UpdateButtonStripExtraView() { |
| save_in_chrome_checkbox_->SetVisible( |
| delegate_->ShouldOfferToSaveInChrome()); |
| |
| gfx::Image image = delegate_->ButtonStripImage(); |
| button_strip_image_->SetVisible(!image.IsEmpty()); |
| button_strip_image_->SetImage(image.AsImageSkia()); |
| |
| autocheckout_progress_bar_view_->SetVisible( |
| delegate_->ShouldShowProgressBar()); |
| } |
| |
| void AutofillDialogViews::ContentsPreferredSizeChanged() { |
| if (GetWidget()) { |
| GetWidget()->SetSize(GetWidget()->non_client_view()->GetPreferredSize()); |
| // If the above line does not cause the dialog's size to change, |contents_| |
| // may not be laid out. This will trigger a layout only if it's needed. |
| SetBoundsRect(bounds()); |
| } |
| } |
| |
| AutofillDialogViews::DetailsGroup* AutofillDialogViews::GroupForSection( |
| DialogSection section) { |
| return &detail_groups_.find(section)->second; |
| } |
| |
| AutofillDialogViews::DetailsGroup* AutofillDialogViews::GroupForView( |
| views::View* view) { |
| DCHECK(view); |
| |
| for (DetailGroupMap::iterator iter = detail_groups_.begin(); |
| iter != detail_groups_.end(); ++iter) { |
| DetailsGroup* group = &iter->second; |
| if (view->parent() == group->manual_input) |
| return group; |
| |
| views::View* decorated = |
| view->GetAncestorWithClassName(DecoratedTextfield::kViewClassName); |
| |
| // 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 (decorated && |
| decorated == group->suggested_info->decorated_textfield()) { |
| return group; |
| } |
| } |
| return NULL; |
| } |
| |
| views::Textfield* AutofillDialogViews::TextfieldForInput( |
| const DetailInput& input) { |
| for (DetailGroupMap::iterator iter = detail_groups_.begin(); |
| iter != detail_groups_.end(); ++iter) { |
| const DetailsGroup& group = iter->second; |
| TextfieldMap::const_iterator text_mapping = group.textfields.find(&input); |
| if (text_mapping != group.textfields.end()) |
| return text_mapping->second; |
| } |
| |
| return NULL; |
| } |
| |
| views::Combobox* AutofillDialogViews::ComboboxForInput( |
| const DetailInput& input) { |
| for (DetailGroupMap::iterator iter = detail_groups_.begin(); |
| iter != detail_groups_.end(); ++iter) { |
| const DetailsGroup& group = iter->second; |
| ComboboxMap::const_iterator combo_mapping = group.comboboxes.find(&input); |
| if (combo_mapping != group.comboboxes.end()) |
| return combo_mapping->second; |
| } |
| |
| return NULL; |
| } |
| |
| void AutofillDialogViews::DetailsContainerBoundsChanged() { |
| if (error_bubble_) |
| error_bubble_->UpdatePosition(); |
| } |
| |
| AutofillDialogViews::DetailsGroup::DetailsGroup(DialogSection section) |
| : section(section), |
| container(NULL), |
| manual_input(NULL), |
| suggested_info(NULL), |
| suggested_button(NULL) {} |
| |
| AutofillDialogViews::DetailsGroup::~DetailsGroup() {} |
| |
| } // namespace autofill |