// 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 #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/view_targeter.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 label_view; if (data.HasCheckbox()) { scoped_ptr 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 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 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_->SetMinSize(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( 7 + 15 * (255 - background_luminance) / 255); int subtle_border_alpha = static_cast( 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(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& 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 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); SetEventTargeter( scoped_ptr(new views::ViewTargeter(this))); } 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::TargetForRect( views::View* root, const gfx::Rect& rect) { CHECK_EQ(root, this); views::View* handler = views::ViewTargeterDelegate::TargetForRect(root, 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& 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& 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( 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(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 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(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 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(view)->invalid()) { return; } views::View* input_view = GetAncestralInputView(view); std::map::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::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