autofill_dialog_views.cc revision 03b57e008b61dfcb1fbad3aea950ae0e001748b0
1// Copyright (c) 2012 The Chromium Authors. All rights reserved. 2// Use of this source code is governed by a BSD-style license that can be 3// found in the LICENSE file. 4 5#include "chrome/browser/ui/views/autofill/autofill_dialog_views.h" 6 7#include <utility> 8 9#include "base/bind.h" 10#include "base/location.h" 11#include "base/strings/utf_string_conversions.h" 12#include "chrome/browser/profiles/profile.h" 13#include "chrome/browser/ui/autofill/autofill_dialog_sign_in_delegate.h" 14#include "chrome/browser/ui/autofill/autofill_dialog_view_delegate.h" 15#include "chrome/browser/ui/autofill/loading_animation.h" 16#include "chrome/browser/ui/views/autofill/expanding_textfield.h" 17#include "chrome/browser/ui/views/autofill/info_bubble.h" 18#include "chrome/browser/ui/views/autofill/tooltip_icon.h" 19#include "chrome/browser/ui/views/constrained_window_views.h" 20#include "components/autofill/content/browser/wallet/wallet_service_url.h" 21#include "components/autofill/core/browser/autofill_type.h" 22#include "components/web_modal/web_contents_modal_dialog_host.h" 23#include "components/web_modal/web_contents_modal_dialog_manager.h" 24#include "components/web_modal/web_contents_modal_dialog_manager_delegate.h" 25#include "content/public/browser/native_web_keyboard_event.h" 26#include "content/public/browser/navigation_controller.h" 27#include "content/public/browser/web_contents.h" 28#include "grit/theme_resources.h" 29#include "third_party/skia/include/core/SkColor.h" 30#include "ui/base/models/combobox_model.h" 31#include "ui/base/models/menu_model.h" 32#include "ui/base/resource/resource_bundle.h" 33#include "ui/events/event_handler.h" 34#include "ui/gfx/animation/animation_delegate.h" 35#include "ui/gfx/canvas.h" 36#include "ui/gfx/color_utils.h" 37#include "ui/gfx/font_list.h" 38#include "ui/gfx/path.h" 39#include "ui/gfx/point.h" 40#include "ui/gfx/skia_util.h" 41#include "ui/resources/grit/ui_resources.h" 42#include "ui/views/background.h" 43#include "ui/views/border.h" 44#include "ui/views/bubble/bubble_border.h" 45#include "ui/views/bubble/bubble_frame_view.h" 46#include "ui/views/controls/button/blue_button.h" 47#include "ui/views/controls/button/checkbox.h" 48#include "ui/views/controls/button/label_button.h" 49#include "ui/views/controls/button/label_button_border.h" 50#include "ui/views/controls/button/menu_button.h" 51#include "ui/views/controls/combobox/combobox.h" 52#include "ui/views/controls/image_view.h" 53#include "ui/views/controls/label.h" 54#include "ui/views/controls/link.h" 55#include "ui/views/controls/menu/menu_runner.h" 56#include "ui/views/controls/separator.h" 57#include "ui/views/controls/styled_label.h" 58#include "ui/views/controls/textfield/textfield.h" 59#include "ui/views/controls/webview/webview.h" 60#include "ui/views/layout/box_layout.h" 61#include "ui/views/layout/fill_layout.h" 62#include "ui/views/layout/grid_layout.h" 63#include "ui/views/layout/layout_constants.h" 64#include "ui/views/painter.h" 65#include "ui/views/view_targeter.h" 66#include "ui/views/widget/widget.h" 67#include "ui/views/window/dialog_client_view.h" 68#include "ui/views/window/non_client_view.h" 69 70namespace autofill { 71 72namespace { 73 74// The width for the section container. 75const int kSectionContainerWidth = 440; 76 77// The minimum useful height of the contents area of the dialog. 78const int kMinimumContentsHeight = 101; 79 80// The default height of the loading shield, also its minimum size. 81const int kInitialLoadingShieldHeight = 150; 82 83// Horizontal padding between text and other elements (in pixels). 84const int kAroundTextPadding = 4; 85 86// The space between the edges of a notification bar and the text within (in 87// pixels). 88const int kNotificationPadding = 17; 89 90// Vertical padding above and below each detail section (in pixels). 91const int kDetailSectionVerticalPadding = 10; 92 93const int kArrowHeight = 7; 94const int kArrowWidth = 2 * kArrowHeight; 95 96// The padding inside the edges of the dialog, in pixels. 97const int kDialogEdgePadding = 20; 98 99// The vertical padding between rows of manual inputs (in pixels). 100const int kManualInputRowPadding = 10; 101 102// Slight shading for mouse hover and legal document background. 103SkColor kShadingColor = SkColorSetARGB(7, 0, 0, 0); 104 105// A border color for the legal document view. 106SkColor kSubtleBorderColor = SkColorSetARGB(10, 0, 0, 0); 107 108// The top and bottom padding, in pixels, for the suggestions menu dropdown 109// arrows. 110const int kMenuButtonTopInset = 3; 111const int kMenuButtonBottomInset = 6; 112 113// The height in pixels of the padding above and below the overlay message view. 114const int kOverlayMessageVerticalPadding = 34; 115 116// Spacing below image and above text messages in overlay view. 117const int kOverlayImageBottomMargin = 100; 118 119const char kNotificationAreaClassName[] = "autofill/NotificationArea"; 120const char kOverlayViewClassName[] = "autofill/OverlayView"; 121const char kSectionContainerClassName[] = "autofill/SectionContainer"; 122const char kSuggestedButtonClassName[] = "autofill/SuggestedButton"; 123 124// Draws an arrow at the top of |canvas| pointing to |tip_x|. 125void DrawArrow(gfx::Canvas* canvas, 126 int tip_x, 127 const SkColor& fill_color, 128 const SkColor& stroke_color) { 129 const int arrow_half_width = kArrowWidth / 2.0f; 130 131 SkPath arrow; 132 arrow.moveTo(tip_x - arrow_half_width, kArrowHeight); 133 arrow.lineTo(tip_x, 0); 134 arrow.lineTo(tip_x + arrow_half_width, kArrowHeight); 135 136 SkPaint fill_paint; 137 fill_paint.setColor(fill_color); 138 canvas->DrawPath(arrow, fill_paint); 139 140 if (stroke_color != SK_ColorTRANSPARENT) { 141 SkPaint stroke_paint; 142 stroke_paint.setColor(stroke_color); 143 stroke_paint.setStyle(SkPaint::kStroke_Style); 144 canvas->DrawPath(arrow, stroke_paint); 145 } 146} 147 148void SelectComboboxValueOrSetToDefault(views::Combobox* combobox, 149 const base::string16& value) { 150 if (!combobox->SelectValue(value)) 151 combobox->SetSelectedIndex(combobox->model()->GetDefaultIndex()); 152} 153 154// This class handles layout for the first row of a SuggestionView. 155// It exists to circumvent shortcomings of GridLayout and BoxLayout (namely that 156// the former doesn't fully respect child visibility, and that the latter won't 157// expand a single child). 158class SectionRowView : public views::View { 159 public: 160 SectionRowView() { SetBorder(views::Border::CreateEmptyBorder(10, 0, 0, 0)); } 161 162 virtual ~SectionRowView() {} 163 164 // views::View implementation: 165 virtual gfx::Size GetPreferredSize() const OVERRIDE { 166 int height = 0; 167 int width = 0; 168 for (int i = 0; i < child_count(); ++i) { 169 if (child_at(i)->visible()) { 170 if (width > 0) 171 width += kAroundTextPadding; 172 173 gfx::Size size = child_at(i)->GetPreferredSize(); 174 height = std::max(height, size.height()); 175 width += size.width(); 176 } 177 } 178 179 gfx::Insets insets = GetInsets(); 180 return gfx::Size(width + insets.width(), height + insets.height()); 181 } 182 183 virtual void Layout() OVERRIDE { 184 const gfx::Rect bounds = GetContentsBounds(); 185 186 // Icon is left aligned. 187 int start_x = bounds.x(); 188 views::View* icon = child_at(0); 189 if (icon->visible()) { 190 icon->SizeToPreferredSize(); 191 icon->SetX(start_x); 192 icon->SetY(bounds.y() + 193 (bounds.height() - icon->bounds().height()) / 2); 194 start_x += icon->bounds().width() + kAroundTextPadding; 195 } 196 197 // Textfield is right aligned. 198 int end_x = bounds.width(); 199 views::View* textfield = child_at(2); 200 if (textfield->visible()) { 201 const int preferred_width = textfield->GetPreferredSize().width(); 202 textfield->SetBounds(bounds.width() - preferred_width, bounds.y(), 203 preferred_width, bounds.height()); 204 end_x = textfield->bounds().x() - kAroundTextPadding; 205 } 206 207 // Label takes up all the space in between. 208 views::View* label = child_at(1); 209 if (label->visible()) 210 label->SetBounds(start_x, bounds.y(), end_x - start_x, bounds.height()); 211 212 views::View::Layout(); 213 } 214 215 private: 216 DISALLOW_COPY_AND_ASSIGN(SectionRowView); 217}; 218 219// A view that propagates visibility and preferred size changes. 220class LayoutPropagationView : public views::View { 221 public: 222 LayoutPropagationView() {} 223 virtual ~LayoutPropagationView() {} 224 225 protected: 226 virtual void ChildVisibilityChanged(views::View* child) OVERRIDE { 227 PreferredSizeChanged(); 228 } 229 virtual void ChildPreferredSizeChanged(views::View* child) OVERRIDE { 230 PreferredSizeChanged(); 231 } 232 233 private: 234 DISALLOW_COPY_AND_ASSIGN(LayoutPropagationView); 235}; 236 237// A View for a single notification banner. 238class NotificationView : public views::View, 239 public views::ButtonListener, 240 public views::StyledLabelListener { 241 public: 242 NotificationView(const DialogNotification& data, 243 AutofillDialogViewDelegate* delegate) 244 : data_(data), 245 delegate_(delegate), 246 checkbox_(NULL) { 247 scoped_ptr<views::View> label_view; 248 if (data.HasCheckbox()) { 249 scoped_ptr<views::Checkbox> checkbox( 250 new views::Checkbox(base::string16())); 251 checkbox->SetText(data.display_text()); 252 checkbox->SetTextMultiLine(true); 253 checkbox->SetHorizontalAlignment(gfx::ALIGN_LEFT); 254 checkbox->SetTextColor(views::Button::STATE_NORMAL, 255 data.GetTextColor()); 256 checkbox->SetTextColor(views::Button::STATE_HOVERED, 257 data.GetTextColor()); 258 checkbox->SetChecked(data.checked()); 259 checkbox->set_listener(this); 260 checkbox_ = checkbox.get(); 261 label_view.reset(checkbox.release()); 262 } else { 263 scoped_ptr<views::StyledLabel> label(new views::StyledLabel( 264 data.display_text(), this)); 265 label->set_auto_color_readability_enabled(false); 266 267 views::StyledLabel::RangeStyleInfo text_style; 268 text_style.color = data.GetTextColor(); 269 270 if (data.link_range().is_empty()) { 271 label->AddStyleRange(gfx::Range(0, data.display_text().size()), 272 text_style); 273 } else { 274 gfx::Range prefix_range(0, data.link_range().start()); 275 if (!prefix_range.is_empty()) 276 label->AddStyleRange(prefix_range, text_style); 277 278 label->AddStyleRange( 279 data.link_range(), 280 views::StyledLabel::RangeStyleInfo::CreateForLink()); 281 282 gfx::Range suffix_range(data.link_range().end(), 283 data.display_text().size()); 284 if (!suffix_range.is_empty()) 285 label->AddStyleRange(suffix_range, text_style); 286 } 287 288 label_view.reset(label.release()); 289 } 290 291 AddChildView(label_view.release()); 292 293 if (!data.tooltip_text().empty()) 294 AddChildView(new TooltipIcon(data.tooltip_text())); 295 296 set_background( 297 views::Background::CreateSolidBackground(data.GetBackgroundColor())); 298 SetBorder(views::Border::CreateSolidSidedBorder( 299 1, 0, 1, 0, data.GetBorderColor())); 300 } 301 302 virtual ~NotificationView() {} 303 304 views::Checkbox* checkbox() { 305 return checkbox_; 306 } 307 308 // views::View implementation. 309 virtual gfx::Insets GetInsets() const OVERRIDE { 310 int vertical_padding = kNotificationPadding; 311 if (checkbox_) 312 vertical_padding -= 3; 313 return gfx::Insets(vertical_padding, kDialogEdgePadding, 314 vertical_padding, kDialogEdgePadding); 315 } 316 317 virtual int GetHeightForWidth(int width) const OVERRIDE { 318 int label_width = width - GetInsets().width(); 319 if (child_count() > 1) { 320 const views::View* tooltip_icon = child_at(1); 321 label_width -= tooltip_icon->GetPreferredSize().width() + 322 kDialogEdgePadding; 323 } 324 325 return child_at(0)->GetHeightForWidth(label_width) + GetInsets().height(); 326 } 327 328 virtual void Layout() OVERRIDE { 329 // Surprisingly, GetContentsBounds() doesn't consult GetInsets(). 330 gfx::Rect bounds = GetLocalBounds(); 331 bounds.Inset(GetInsets()); 332 int right_bound = bounds.right(); 333 334 if (child_count() > 1) { 335 // The icon takes up the entire vertical space and an extra 20px on 336 // each side. This increases the hover target for the tooltip. 337 views::View* tooltip_icon = child_at(1); 338 gfx::Size icon_size = tooltip_icon->GetPreferredSize(); 339 int icon_width = icon_size.width() + kDialogEdgePadding; 340 right_bound -= icon_width; 341 tooltip_icon->SetBounds( 342 right_bound, 0, 343 icon_width + kDialogEdgePadding, GetLocalBounds().height()); 344 } 345 346 child_at(0)->SetBounds(bounds.x(), bounds.y(), 347 right_bound - bounds.x(), bounds.height()); 348 } 349 350 // views::ButtonListener implementation. 351 virtual void ButtonPressed(views::Button* sender, 352 const ui::Event& event) OVERRIDE { 353 DCHECK_EQ(sender, checkbox_); 354 delegate_->NotificationCheckboxStateChanged(data_.type(), 355 checkbox_->checked()); 356 } 357 358 // views::StyledLabelListener implementation. 359 virtual void StyledLabelLinkClicked(const gfx::Range& range, int event_flags) 360 OVERRIDE { 361 delegate_->LinkClicked(data_.link_url()); 362 } 363 364 private: 365 // The model data for this notification. 366 DialogNotification data_; 367 368 // The delegate that handles interaction with |this|. 369 AutofillDialogViewDelegate* delegate_; 370 371 // The checkbox associated with this notification, or NULL if there is none. 372 views::Checkbox* checkbox_; 373 374 DISALLOW_COPY_AND_ASSIGN(NotificationView); 375}; 376 377// A view that displays a loading message with some dancing dots. 378class LoadingAnimationView : public views::View, 379 public gfx::AnimationDelegate { 380 public: 381 explicit LoadingAnimationView(const base::string16& text) : 382 container_(new views::View()) { 383 AddChildView(container_); 384 container_->SetLayoutManager( 385 new views::BoxLayout(views::BoxLayout::kHorizontal, 0, 0, 0)); 386 387 const gfx::FontList& font_list = 388 ui::ResourceBundle::GetSharedInstance().GetFontList( 389 ui::ResourceBundle::LargeFont); 390 animation_.reset(new LoadingAnimation(this, font_list.GetHeight())); 391 392 container_->AddChildView(new views::Label(text, font_list)); 393 394 for (size_t i = 0; i < 3; ++i) { 395 container_->AddChildView( 396 new views::Label(base::ASCIIToUTF16("."), font_list)); 397 } 398 } 399 400 virtual ~LoadingAnimationView() {} 401 402 // views::View implementation. 403 virtual void SetVisible(bool visible) OVERRIDE { 404 if (visible) 405 animation_->Start(); 406 else 407 animation_->Reset(); 408 409 views::View::SetVisible(visible); 410 } 411 412 virtual void Layout() OVERRIDE { 413 gfx::Size container_size = container_->GetPreferredSize(); 414 gfx::Rect container_bounds((width() - container_size.width()) / 2, 415 (height() - container_size.height()) / 2, 416 container_size.width(), 417 container_size.height()); 418 container_->SetBoundsRect(container_bounds); 419 container_->Layout(); 420 421 for (size_t i = 0; i < 3; ++i) { 422 views::View* dot = container_->child_at(i + 1); 423 dot->SetY(dot->y() + animation_->GetCurrentValueForDot(i)); 424 } 425 } 426 427 virtual void OnNativeThemeChanged(const ui::NativeTheme* theme) OVERRIDE { 428 set_background(views::Background::CreateSolidBackground( 429 theme->GetSystemColor(ui::NativeTheme::kColorId_DialogBackground))); 430 } 431 432 // gfx::AnimationDelegate implementation. 433 virtual void AnimationProgressed(const gfx::Animation* animation) OVERRIDE { 434 DCHECK_EQ(animation, animation_.get()); 435 Layout(); 436 } 437 438 private: 439 // Contains the "Loading" label and the dots. 440 views::View* container_; 441 442 scoped_ptr<LoadingAnimation> animation_; 443 444 DISALLOW_COPY_AND_ASSIGN(LoadingAnimationView); 445}; 446 447// Gets either the Combobox or ExpandingTextfield that is an ancestor (including 448// self) of |view|. 449views::View* GetAncestralInputView(views::View* view) { 450 if (view->GetClassName() == views::Combobox::kViewClassName) 451 return view; 452 453 return view->GetAncestorWithClassName(ExpandingTextfield::kViewClassName); 454} 455 456// A class that informs |delegate_| when an unhandled mouse press occurs. 457class MousePressedHandler : public ui::EventHandler { 458 public: 459 explicit MousePressedHandler(AutofillDialogViewDelegate* delegate) 460 : delegate_(delegate) {} 461 462 // ui::EventHandler implementation. 463 virtual void OnMouseEvent(ui::MouseEvent* event) OVERRIDE { 464 if (event->type() == ui::ET_MOUSE_PRESSED && !event->handled()) 465 delegate_->FocusMoved(); 466 } 467 468 private: 469 AutofillDialogViewDelegate* const delegate_; 470 471 DISALLOW_COPY_AND_ASSIGN(MousePressedHandler); 472}; 473 474} // namespace 475 476// AutofillDialogViews::AccountChooser ----------------------------------------- 477 478AutofillDialogViews::AccountChooser::AccountChooser( 479 AutofillDialogViewDelegate* delegate) 480 : image_(new views::ImageView()), 481 menu_button_(new views::MenuButton(NULL, base::string16(), this, true)), 482 link_(new views::Link()), 483 delegate_(delegate) { 484 SetBorder(views::Border::CreateEmptyBorder(0, 0, 0, 10)); 485 SetLayoutManager( 486 new views::BoxLayout(views::BoxLayout::kHorizontal, 0, 0, 487 kAroundTextPadding)); 488 AddChildView(image_); 489 490 menu_button_->set_background(NULL); 491 menu_button_->SetBorder(views::Border::NullBorder()); 492 gfx::Insets insets = GetInsets(); 493 menu_button_->SetFocusPainter( 494 views::Painter::CreateDashedFocusPainterWithInsets(insets)); 495 menu_button_->SetFocusable(true); 496 AddChildView(menu_button_); 497 498 link_->set_listener(this); 499 AddChildView(link_); 500} 501 502AutofillDialogViews::AccountChooser::~AccountChooser() {} 503 504void AutofillDialogViews::AccountChooser::Update() { 505 SetVisible(delegate_->ShouldShowAccountChooser()); 506 507 gfx::Image icon = delegate_->AccountChooserImage(); 508 image_->SetImage(icon.AsImageSkia()); 509 menu_button_->SetText(delegate_->AccountChooserText()); 510 menu_button_->SetMinSize(gfx::Size()); 511 512 bool show_link = !delegate_->MenuModelForAccountChooser(); 513 menu_button_->SetVisible(!show_link); 514 link_->SetText(delegate_->SignInLinkText()); 515 link_->SetVisible(show_link); 516 517 menu_runner_.reset(); 518 519 PreferredSizeChanged(); 520} 521 522void AutofillDialogViews::AccountChooser::OnMenuButtonClicked( 523 views::View* source, 524 const gfx::Point& point) { 525 DCHECK_EQ(menu_button_, source); 526 527 ui::MenuModel* model = delegate_->MenuModelForAccountChooser(); 528 if (!model) 529 return; 530 531 menu_runner_.reset(new views::MenuRunner(model, 0)); 532 if (menu_runner_->RunMenuAt(source->GetWidget(), 533 NULL, 534 source->GetBoundsInScreen(), 535 views::MENU_ANCHOR_TOPRIGHT, 536 ui::MENU_SOURCE_NONE) == 537 views::MenuRunner::MENU_DELETED) { 538 return; 539 } 540} 541 542views::View* AutofillDialogViews::GetLoadingShieldForTesting() { 543 return loading_shield_; 544} 545 546views::WebView* AutofillDialogViews::GetSignInWebViewForTesting() { 547 return sign_in_web_view_; 548} 549 550views::View* AutofillDialogViews::GetNotificationAreaForTesting() { 551 return notification_area_; 552} 553 554views::View* AutofillDialogViews::GetScrollableAreaForTesting() { 555 return scrollable_area_; 556} 557 558void AutofillDialogViews::AccountChooser::LinkClicked(views::Link* source, 559 int event_flags) { 560 delegate_->SignInLinkClicked(); 561} 562 563// AutofillDialogViews::OverlayView -------------------------------------------- 564 565AutofillDialogViews::OverlayView::OverlayView( 566 AutofillDialogViewDelegate* delegate) 567 : delegate_(delegate), 568 image_view_(new views::ImageView()), 569 message_view_(new views::Label()) { 570 message_view_->SetAutoColorReadabilityEnabled(false); 571 message_view_->SetMultiLine(true); 572 573 AddChildView(image_view_); 574 AddChildView(message_view_); 575} 576 577AutofillDialogViews::OverlayView::~OverlayView() {} 578 579int AutofillDialogViews::OverlayView::GetHeightForContentsForWidth(int width) { 580 // In this case, 0 means "no preference". 581 if (!message_view_->visible()) 582 return 0; 583 584 return kOverlayImageBottomMargin + 585 views::kButtonVEdgeMarginNew + 586 message_view_->GetHeightForWidth(width) + 587 image_view_->GetHeightForWidth(width); 588} 589 590void AutofillDialogViews::OverlayView::UpdateState() { 591 const DialogOverlayState& state = delegate_->GetDialogOverlay(); 592 593 if (state.image.IsEmpty()) { 594 SetVisible(false); 595 return; 596 } 597 598 image_view_->SetImage(state.image.ToImageSkia()); 599 600 message_view_->SetVisible(!state.string.text.empty()); 601 message_view_->SetText(state.string.text); 602 message_view_->SetFontList(state.string.font_list); 603 message_view_->SetEnabledColor(GetNativeTheme()->GetSystemColor( 604 ui::NativeTheme::kColorId_TextfieldReadOnlyColor)); 605 606 message_view_->SetBorder( 607 views::Border::CreateEmptyBorder(kOverlayMessageVerticalPadding, 608 kDialogEdgePadding, 609 kOverlayMessageVerticalPadding, 610 kDialogEdgePadding)); 611 612 SetVisible(true); 613} 614 615gfx::Insets AutofillDialogViews::OverlayView::GetInsets() const { 616 return gfx::Insets(12, 12, 12, 12); 617} 618 619void AutofillDialogViews::OverlayView::Layout() { 620 gfx::Rect bounds = ContentBoundsSansBubbleBorder(); 621 if (!message_view_->visible()) { 622 image_view_->SetBoundsRect(bounds); 623 return; 624 } 625 626 int message_height = message_view_->GetHeightForWidth(bounds.width()); 627 int y = bounds.bottom() - message_height; 628 message_view_->SetBounds(bounds.x(), y, bounds.width(), message_height); 629 630 gfx::Size image_size = image_view_->GetPreferredSize(); 631 y -= image_size.height() + kOverlayImageBottomMargin; 632 image_view_->SetBounds(bounds.x(), y, bounds.width(), image_size.height()); 633} 634 635const char* AutofillDialogViews::OverlayView::GetClassName() const { 636 return kOverlayViewClassName; 637} 638 639void AutofillDialogViews::OverlayView::OnPaint(gfx::Canvas* canvas) { 640 // BubbleFrameView doesn't mask the window, it just draws the border via 641 // image assets. Match that rounding here. 642 gfx::Rect rect = ContentBoundsSansBubbleBorder(); 643 const SkScalar kCornerRadius = SkIntToScalar( 644 GetBubbleBorder() ? GetBubbleBorder()->GetBorderCornerRadius() : 2); 645 gfx::Path window_mask; 646 window_mask.addRoundRect(gfx::RectToSkRect(rect), 647 kCornerRadius, kCornerRadius); 648 canvas->ClipPath(window_mask, false); 649 650 OnPaintBackground(canvas); 651 652 // Draw the arrow, border, and fill for the bottom area. 653 if (message_view_->visible()) { 654 const int arrow_half_width = kArrowWidth / 2.0f; 655 SkPath arrow; 656 int y = message_view_->y() - 1; 657 // Note that we purposely draw slightly outside of |rect| so that the 658 // stroke is hidden on the sides. 659 arrow.moveTo(rect.x() - 1, y); 660 arrow.rLineTo(rect.width() / 2 - arrow_half_width, 0); 661 arrow.rLineTo(arrow_half_width, -kArrowHeight); 662 arrow.rLineTo(arrow_half_width, kArrowHeight); 663 arrow.lineTo(rect.right() + 1, y); 664 arrow.lineTo(rect.right() + 1, rect.bottom() + 1); 665 arrow.lineTo(rect.x() - 1, rect.bottom() + 1); 666 arrow.close(); 667 668 // The mocked alpha blends were 7 for background & 10 for the border against 669 // a very bright background. The eye perceives luminance differences of 670 // darker colors much less than lighter colors, so increase the alpha blend 671 // amount the darker the color (lower the luminance). 672 SkPaint paint; 673 SkColor background_color = background()->get_color(); 674 int background_luminance = 675 color_utils::GetLuminanceForColor(background_color); 676 int background_alpha = static_cast<int>( 677 7 + 15 * (255 - background_luminance) / 255); 678 int subtle_border_alpha = static_cast<int>( 679 10 + 20 * (255 - background_luminance) / 255); 680 681 paint.setColor(color_utils::BlendTowardOppositeLuminance( 682 background_color, background_alpha)); 683 paint.setStyle(SkPaint::kFill_Style); 684 canvas->DrawPath(arrow, paint); 685 paint.setColor(color_utils::BlendTowardOppositeLuminance( 686 background_color, subtle_border_alpha)); 687 paint.setStyle(SkPaint::kStroke_Style); 688 canvas->DrawPath(arrow, paint); 689 } 690 691 PaintChildren(canvas, views::CullSet()); 692} 693 694void AutofillDialogViews::OverlayView::OnNativeThemeChanged( 695 const ui::NativeTheme* theme) { 696 set_background(views::Background::CreateSolidBackground( 697 theme->GetSystemColor(ui::NativeTheme::kColorId_DialogBackground))); 698} 699 700views::BubbleBorder* AutofillDialogViews::OverlayView::GetBubbleBorder() { 701 views::View* frame = GetWidget()->non_client_view()->frame_view(); 702 std::string bubble_frame_view_name(views::BubbleFrameView::kViewClassName); 703 if (frame->GetClassName() == bubble_frame_view_name) 704 return static_cast<views::BubbleFrameView*>(frame)->bubble_border(); 705 NOTREACHED(); 706 return NULL; 707} 708 709gfx::Rect AutofillDialogViews::OverlayView::ContentBoundsSansBubbleBorder() { 710 gfx::Rect bounds = GetContentsBounds(); 711 int bubble_width = 5; 712 if (GetBubbleBorder()) 713 bubble_width = GetBubbleBorder()->GetBorderThickness(); 714 bounds.Inset(bubble_width, bubble_width, bubble_width, bubble_width); 715 return bounds; 716} 717 718// AutofillDialogViews::NotificationArea --------------------------------------- 719 720AutofillDialogViews::NotificationArea::NotificationArea( 721 AutofillDialogViewDelegate* delegate) 722 : delegate_(delegate) { 723 // Reserve vertical space for the arrow (regardless of whether one exists). 724 // The -1 accounts for the border. 725 SetBorder(views::Border::CreateEmptyBorder(kArrowHeight - 1, 0, 0, 0)); 726 727 views::BoxLayout* box_layout = 728 new views::BoxLayout(views::BoxLayout::kVertical, 0, 0, 0); 729 SetLayoutManager(box_layout); 730} 731 732AutofillDialogViews::NotificationArea::~NotificationArea() {} 733 734void AutofillDialogViews::NotificationArea::SetNotifications( 735 const std::vector<DialogNotification>& notifications) { 736 notifications_ = notifications; 737 738 RemoveAllChildViews(true); 739 740 if (notifications_.empty()) 741 return; 742 743 for (size_t i = 0; i < notifications_.size(); ++i) { 744 const DialogNotification& notification = notifications_[i]; 745 scoped_ptr<NotificationView> view(new NotificationView(notification, 746 delegate_)); 747 748 AddChildView(view.release()); 749 } 750 751 PreferredSizeChanged(); 752} 753 754gfx::Size AutofillDialogViews::NotificationArea::GetPreferredSize() const { 755 gfx::Size size = views::View::GetPreferredSize(); 756 // Ensure that long notifications wrap and don't enlarge the dialog. 757 size.set_width(1); 758 return size; 759} 760 761const char* AutofillDialogViews::NotificationArea::GetClassName() const { 762 return kNotificationAreaClassName; 763} 764 765void AutofillDialogViews::NotificationArea::PaintChildren( 766 gfx::Canvas* canvas, 767 const views::CullSet& cull_set) { 768} 769 770void AutofillDialogViews::NotificationArea::OnPaint(gfx::Canvas* canvas) { 771 views::View::OnPaint(canvas); 772 views::View::PaintChildren(canvas, views::CullSet()); 773 774 if (HasArrow()) { 775 DrawArrow( 776 canvas, 777 GetMirroredXInView(width() - arrow_centering_anchor_->width() / 2.0f), 778 notifications_[0].GetBackgroundColor(), 779 notifications_[0].GetBorderColor()); 780 } 781} 782 783void AutofillDialogViews::OnWidgetDestroying(views::Widget* widget) { 784 if (widget == window_) 785 window_->GetRootView()->RemovePostTargetHandler(event_handler_.get()); 786} 787 788void AutofillDialogViews::OnWidgetClosing(views::Widget* widget) { 789 observer_.Remove(widget); 790 if (error_bubble_ && error_bubble_->GetWidget() == widget) 791 error_bubble_ = NULL; 792} 793 794void AutofillDialogViews::OnWidgetBoundsChanged(views::Widget* widget, 795 const gfx::Rect& new_bounds) { 796 if (error_bubble_ && error_bubble_->GetWidget() == widget) 797 return; 798 799 // Notify the web contents of its new auto-resize limits. 800 if (sign_in_delegate_ && sign_in_web_view_->visible()) { 801 sign_in_delegate_->UpdateLimitsAndEnableAutoResize( 802 GetMinimumSignInViewSize(), GetMaximumSignInViewSize()); 803 } 804 HideErrorBubble(); 805} 806 807bool AutofillDialogViews::NotificationArea::HasArrow() { 808 return !notifications_.empty() && notifications_[0].HasArrow() && 809 arrow_centering_anchor_.get(); 810} 811 812// AutofillDialogViews::SectionContainer --------------------------------------- 813 814AutofillDialogViews::SectionContainer::SectionContainer( 815 const base::string16& label, 816 views::View* controls, 817 views::Button* proxy_button) 818 : proxy_button_(proxy_button), 819 forward_mouse_events_(false) { 820 set_notify_enter_exit_on_child(true); 821 822 SetBorder(views::Border::CreateEmptyBorder(kDetailSectionVerticalPadding, 823 kDialogEdgePadding, 824 kDetailSectionVerticalPadding, 825 kDialogEdgePadding)); 826 827 ui::ResourceBundle& rb = ui::ResourceBundle::GetSharedInstance(); 828 views::Label* label_view = new views::Label( 829 label, rb.GetFontList(ui::ResourceBundle::BoldFont)); 830 label_view->SetHorizontalAlignment(gfx::ALIGN_LEFT); 831 832 views::View* label_bar = new views::View(); 833 views::GridLayout* label_bar_layout = new views::GridLayout(label_bar); 834 label_bar->SetLayoutManager(label_bar_layout); 835 const int kColumnSetId = 0; 836 views::ColumnSet* columns = label_bar_layout->AddColumnSet(kColumnSetId); 837 columns->AddColumn( 838 views::GridLayout::LEADING, 839 views::GridLayout::LEADING, 840 0, 841 views::GridLayout::FIXED, 842 kSectionContainerWidth - proxy_button->GetPreferredSize().width(), 843 0); 844 columns->AddColumn(views::GridLayout::LEADING, 845 views::GridLayout::LEADING, 846 0, 847 views::GridLayout::USE_PREF, 848 0, 849 0); 850 label_bar_layout->StartRow(0, kColumnSetId); 851 label_bar_layout->AddView(label_view); 852 label_bar_layout->AddView(proxy_button); 853 854 SetLayoutManager(new views::BoxLayout(views::BoxLayout::kVertical, 0, 0, 0)); 855 AddChildView(label_bar); 856 AddChildView(controls); 857 858 SetEventTargeter( 859 scoped_ptr<views::ViewTargeter>(new views::ViewTargeter(this))); 860} 861 862AutofillDialogViews::SectionContainer::~SectionContainer() {} 863 864void AutofillDialogViews::SectionContainer::SetActive(bool active) { 865 bool is_active = active && proxy_button_->visible(); 866 if (is_active == !!background()) 867 return; 868 869 set_background(is_active ? 870 views::Background::CreateSolidBackground(kShadingColor) : 871 NULL); 872 SchedulePaint(); 873} 874 875void AutofillDialogViews::SectionContainer::SetForwardMouseEvents( 876 bool forward) { 877 forward_mouse_events_ = forward; 878 if (!forward) 879 set_background(NULL); 880} 881 882const char* AutofillDialogViews::SectionContainer::GetClassName() const { 883 return kSectionContainerClassName; 884} 885 886void AutofillDialogViews::SectionContainer::OnMouseMoved( 887 const ui::MouseEvent& event) { 888 SetActive(ShouldForwardEvent(event)); 889} 890 891void AutofillDialogViews::SectionContainer::OnMouseEntered( 892 const ui::MouseEvent& event) { 893 if (!ShouldForwardEvent(event)) 894 return; 895 896 SetActive(true); 897 proxy_button_->OnMouseEntered(ProxyEvent(event)); 898 SchedulePaint(); 899} 900 901void AutofillDialogViews::SectionContainer::OnMouseExited( 902 const ui::MouseEvent& event) { 903 SetActive(false); 904 if (!ShouldForwardEvent(event)) 905 return; 906 907 proxy_button_->OnMouseExited(ProxyEvent(event)); 908 SchedulePaint(); 909} 910 911bool AutofillDialogViews::SectionContainer::OnMousePressed( 912 const ui::MouseEvent& event) { 913 if (!ShouldForwardEvent(event)) 914 return false; 915 916 return proxy_button_->OnMousePressed(ProxyEvent(event)); 917} 918 919void AutofillDialogViews::SectionContainer::OnMouseReleased( 920 const ui::MouseEvent& event) { 921 if (!ShouldForwardEvent(event)) 922 return; 923 924 proxy_button_->OnMouseReleased(ProxyEvent(event)); 925} 926 927void AutofillDialogViews::SectionContainer::OnGestureEvent( 928 ui::GestureEvent* event) { 929 if (!ShouldForwardEvent(*event)) 930 return; 931 932 proxy_button_->OnGestureEvent(event); 933} 934 935views::View* AutofillDialogViews::SectionContainer::TargetForRect( 936 views::View* root, 937 const gfx::Rect& rect) { 938 CHECK_EQ(root, this); 939 views::View* handler = views::ViewTargeterDelegate::TargetForRect(root, rect); 940 941 // If the event is not in the label bar and there's no background to be 942 // cleared, let normal event handling take place. 943 if (!background() && 944 rect.CenterPoint().y() > child_at(0)->bounds().bottom()) { 945 return handler; 946 } 947 948 // Special case for (CVC) inputs in the suggestion view. 949 if (forward_mouse_events_ && 950 handler->GetAncestorWithClassName(ExpandingTextfield::kViewClassName)) { 951 return handler; 952 } 953 954 // Special case for the proxy button itself. 955 if (handler == proxy_button_) 956 return handler; 957 958 return this; 959} 960 961// static 962ui::MouseEvent AutofillDialogViews::SectionContainer::ProxyEvent( 963 const ui::MouseEvent& event) { 964 ui::MouseEvent event_copy = event; 965 event_copy.set_location(gfx::Point()); 966 return event_copy; 967} 968 969bool AutofillDialogViews::SectionContainer::ShouldForwardEvent( 970 const ui::LocatedEvent& event) { 971 // Always forward events on the label bar. 972 return forward_mouse_events_ || event.y() <= child_at(0)->bounds().bottom(); 973} 974 975// AutofillDialogViews::SuggestedButton ---------------------------------------- 976 977AutofillDialogViews::SuggestedButton::SuggestedButton( 978 views::MenuButtonListener* listener) 979 : views::MenuButton(NULL, base::string16(), listener, false) { 980 const int kFocusBorderWidth = 1; 981 SetBorder(views::Border::CreateEmptyBorder(kMenuButtonTopInset, 982 kFocusBorderWidth, 983 kMenuButtonBottomInset, 984 kFocusBorderWidth)); 985 gfx::Insets insets = GetInsets(); 986 insets += gfx::Insets(-kFocusBorderWidth, -kFocusBorderWidth, 987 -kFocusBorderWidth, -kFocusBorderWidth); 988 SetFocusPainter( 989 views::Painter::CreateDashedFocusPainterWithInsets(insets)); 990 SetFocusable(true); 991} 992 993AutofillDialogViews::SuggestedButton::~SuggestedButton() {} 994 995gfx::Size AutofillDialogViews::SuggestedButton::GetPreferredSize() const { 996 ui::ResourceBundle& rb = ui::ResourceBundle::GetSharedInstance(); 997 gfx::Size size = rb.GetImageNamed(ResourceIDForState()).Size(); 998 const gfx::Insets insets = GetInsets(); 999 size.Enlarge(insets.width(), insets.height()); 1000 return size; 1001} 1002 1003const char* AutofillDialogViews::SuggestedButton::GetClassName() const { 1004 return kSuggestedButtonClassName; 1005} 1006 1007void AutofillDialogViews::SuggestedButton::PaintChildren( 1008 gfx::Canvas* canvas, 1009 const views::CullSet& cull_set) { 1010} 1011 1012void AutofillDialogViews::SuggestedButton::OnPaint(gfx::Canvas* canvas) { 1013 ui::ResourceBundle& rb = ui::ResourceBundle::GetSharedInstance(); 1014 const gfx::Insets insets = GetInsets(); 1015 canvas->DrawImageInt(*rb.GetImageSkiaNamed(ResourceIDForState()), 1016 insets.left(), insets.top()); 1017 views::Painter::PaintFocusPainter(this, canvas, focus_painter()); 1018} 1019 1020int AutofillDialogViews::SuggestedButton::ResourceIDForState() const { 1021 views::Button::ButtonState button_state = state(); 1022 if (button_state == views::Button::STATE_PRESSED) 1023 return IDR_AUTOFILL_DIALOG_MENU_BUTTON_P; 1024 else if (button_state == views::Button::STATE_HOVERED) 1025 return IDR_AUTOFILL_DIALOG_MENU_BUTTON_H; 1026 else if (button_state == views::Button::STATE_DISABLED) 1027 return IDR_AUTOFILL_DIALOG_MENU_BUTTON_D; 1028 DCHECK_EQ(views::Button::STATE_NORMAL, button_state); 1029 return IDR_AUTOFILL_DIALOG_MENU_BUTTON; 1030} 1031 1032// AutofillDialogViews::DetailsContainerView ----------------------------------- 1033 1034AutofillDialogViews::DetailsContainerView::DetailsContainerView( 1035 const base::Closure& callback) 1036 : bounds_changed_callback_(callback), 1037 ignore_layouts_(false) {} 1038 1039AutofillDialogViews::DetailsContainerView::~DetailsContainerView() {} 1040 1041void AutofillDialogViews::DetailsContainerView::OnBoundsChanged( 1042 const gfx::Rect& previous_bounds) { 1043 bounds_changed_callback_.Run(); 1044} 1045 1046void AutofillDialogViews::DetailsContainerView::Layout() { 1047 if (!ignore_layouts_) 1048 views::View::Layout(); 1049} 1050 1051// AutofillDialogViews::SuggestionView ----------------------------------------- 1052 1053AutofillDialogViews::SuggestionView::SuggestionView( 1054 AutofillDialogViews* autofill_dialog) 1055 : label_(new views::Label()), 1056 label_line_2_(new views::Label()), 1057 icon_(new views::ImageView()), 1058 textfield_( 1059 new ExpandingTextfield(base::string16(), 1060 base::string16(), 1061 false, 1062 autofill_dialog)) { 1063 // TODO(estade): Make this the correct color. 1064 SetBorder(views::Border::CreateSolidSidedBorder(1, 0, 0, 0, SK_ColorLTGRAY)); 1065 1066 SectionRowView* label_container = new SectionRowView(); 1067 AddChildView(label_container); 1068 1069 // Label and icon. 1070 label_container->AddChildView(icon_); 1071 label_->SetHorizontalAlignment(gfx::ALIGN_LEFT); 1072 label_container->AddChildView(label_); 1073 1074 // TODO(estade): get the sizing and spacing right on this textfield. 1075 textfield_->SetVisible(false); 1076 textfield_->SetDefaultWidthInCharacters(15); 1077 label_container->AddChildView(textfield_); 1078 1079 label_line_2_->SetHorizontalAlignment(gfx::ALIGN_LEFT); 1080 label_line_2_->SetVisible(false); 1081 label_line_2_->SetLineHeight(22); 1082 label_line_2_->SetMultiLine(true); 1083 AddChildView(label_line_2_); 1084 1085 SetLayoutManager(new views::BoxLayout(views::BoxLayout::kVertical, 0, 0, 7)); 1086} 1087 1088AutofillDialogViews::SuggestionView::~SuggestionView() {} 1089 1090gfx::Size AutofillDialogViews::SuggestionView::GetPreferredSize() const { 1091 // There's no preferred width. The parent's layout should get the preferred 1092 // height from GetHeightForWidth(). 1093 return gfx::Size(); 1094} 1095 1096int AutofillDialogViews::SuggestionView::GetHeightForWidth(int width) const { 1097 int height = 0; 1098 CanUseVerticallyCompactText(width, &height); 1099 return height; 1100} 1101 1102bool AutofillDialogViews::SuggestionView::CanUseVerticallyCompactText( 1103 int available_width, 1104 int* resulting_height) const { 1105 // This calculation may be costly, avoid doing it more than once per width. 1106 if (!calculated_heights_.count(available_width)) { 1107 // Changing the state of |this| now will lead to extra layouts and 1108 // paints we don't want, so create another SuggestionView to calculate 1109 // which label we have room to show. 1110 SuggestionView sizing_view(NULL); 1111 sizing_view.SetLabelText(state_.vertically_compact_text); 1112 sizing_view.SetIcon(state_.icon); 1113 sizing_view.SetTextfield(state_.extra_text, state_.extra_icon); 1114 sizing_view.label_->SetSize(gfx::Size(available_width, 0)); 1115 sizing_view.label_line_2_->SetSize(gfx::Size(available_width, 0)); 1116 1117 // Shortcut |sizing_view|'s GetHeightForWidth() to avoid an infinite loop. 1118 // Its BoxLayout must do these calculations for us. 1119 views::LayoutManager* layout = sizing_view.GetLayoutManager(); 1120 if (layout->GetPreferredSize(&sizing_view).width() <= available_width) { 1121 calculated_heights_[available_width] = std::make_pair( 1122 true, 1123 layout->GetPreferredHeightForWidth(&sizing_view, available_width)); 1124 } else { 1125 sizing_view.SetLabelText(state_.horizontally_compact_text); 1126 calculated_heights_[available_width] = std::make_pair( 1127 false, 1128 layout->GetPreferredHeightForWidth(&sizing_view, available_width)); 1129 } 1130 } 1131 1132 const std::pair<bool, int>& values = calculated_heights_[available_width]; 1133 *resulting_height = values.second; 1134 return values.first; 1135} 1136 1137void AutofillDialogViews::SuggestionView::OnBoundsChanged( 1138 const gfx::Rect& previous_bounds) { 1139 UpdateLabelText(); 1140} 1141 1142void AutofillDialogViews::SuggestionView::SetState( 1143 const SuggestionState& state) { 1144 calculated_heights_.clear(); 1145 state_ = state; 1146 SetVisible(state_.visible); 1147 UpdateLabelText(); 1148 SetIcon(state_.icon); 1149 SetTextfield(state_.extra_text, state_.extra_icon); 1150 PreferredSizeChanged(); 1151} 1152 1153void AutofillDialogViews::SuggestionView::SetLabelText( 1154 const base::string16& text) { 1155 // TODO(estade): does this localize well? 1156 base::string16 line_return(base::ASCIIToUTF16("\n")); 1157 size_t position = text.find(line_return); 1158 if (position == base::string16::npos) { 1159 label_->SetText(text); 1160 label_line_2_->SetVisible(false); 1161 } else { 1162 label_->SetText(text.substr(0, position)); 1163 label_line_2_->SetText(text.substr(position + line_return.length())); 1164 label_line_2_->SetVisible(true); 1165 } 1166} 1167 1168void AutofillDialogViews::SuggestionView::SetIcon( 1169 const gfx::Image& image) { 1170 icon_->SetVisible(!image.IsEmpty()); 1171 icon_->SetImage(image.AsImageSkia()); 1172} 1173 1174void AutofillDialogViews::SuggestionView::SetTextfield( 1175 const base::string16& placeholder_text, 1176 const gfx::Image& icon) { 1177 textfield_->SetPlaceholderText(placeholder_text); 1178 textfield_->SetIcon(icon); 1179 textfield_->SetVisible(!placeholder_text.empty()); 1180} 1181 1182void AutofillDialogViews::SuggestionView::UpdateLabelText() { 1183 int unused; 1184 SetLabelText(CanUseVerticallyCompactText(width(), &unused) ? 1185 state_.vertically_compact_text : 1186 state_.horizontally_compact_text); 1187} 1188 1189// AutofillDialogView ---------------------------------------------------------- 1190 1191// static 1192AutofillDialogView* AutofillDialogView::Create( 1193 AutofillDialogViewDelegate* delegate) { 1194 return new AutofillDialogViews(delegate); 1195} 1196 1197// AutofillDialogViews --------------------------------------------------------- 1198 1199AutofillDialogViews::AutofillDialogViews(AutofillDialogViewDelegate* delegate) 1200 : delegate_(delegate), 1201 updates_scope_(0), 1202 needs_update_(false), 1203 window_(NULL), 1204 notification_area_(NULL), 1205 account_chooser_(NULL), 1206 sign_in_web_view_(NULL), 1207 scrollable_area_(NULL), 1208 details_container_(NULL), 1209 loading_shield_(NULL), 1210 loading_shield_height_(0), 1211 overlay_view_(NULL), 1212 button_strip_extra_view_(NULL), 1213 save_in_chrome_checkbox_(NULL), 1214 save_in_chrome_checkbox_container_(NULL), 1215 button_strip_image_(NULL), 1216 footnote_view_(NULL), 1217 legal_document_view_(NULL), 1218 focus_manager_(NULL), 1219 error_bubble_(NULL), 1220 observer_(this) { 1221 DCHECK(delegate); 1222 detail_groups_.insert(std::make_pair(SECTION_CC, 1223 DetailsGroup(SECTION_CC))); 1224 detail_groups_.insert(std::make_pair(SECTION_BILLING, 1225 DetailsGroup(SECTION_BILLING))); 1226 detail_groups_.insert(std::make_pair(SECTION_CC_BILLING, 1227 DetailsGroup(SECTION_CC_BILLING))); 1228 detail_groups_.insert(std::make_pair(SECTION_SHIPPING, 1229 DetailsGroup(SECTION_SHIPPING))); 1230} 1231 1232AutofillDialogViews::~AutofillDialogViews() { 1233 HideErrorBubble(); 1234 DCHECK(!window_); 1235} 1236 1237void AutofillDialogViews::Show() { 1238 InitChildViews(); 1239 UpdateAccountChooser(); 1240 UpdateNotificationArea(); 1241 UpdateButtonStripExtraView(); 1242 1243 window_ = ShowWebModalDialogViews(this, delegate_->GetWebContents()); 1244 focus_manager_ = window_->GetFocusManager(); 1245 focus_manager_->AddFocusChangeListener(this); 1246 1247 ShowDialogInMode(DETAIL_INPUT); 1248 1249 // Listen for size changes on the browser. 1250 views::Widget* browser_widget = 1251 views::Widget::GetTopLevelWidgetForNativeView( 1252 delegate_->GetWebContents()->GetNativeView()); 1253 observer_.Add(browser_widget); 1254 1255 // Listen for unhandled mouse presses on the non-client view. 1256 event_handler_.reset(new MousePressedHandler(delegate_)); 1257 window_->GetRootView()->AddPostTargetHandler(event_handler_.get()); 1258 observer_.Add(window_); 1259} 1260 1261void AutofillDialogViews::Hide() { 1262 if (window_) 1263 window_->Close(); 1264} 1265 1266void AutofillDialogViews::UpdatesStarted() { 1267 updates_scope_++; 1268} 1269 1270void AutofillDialogViews::UpdatesFinished() { 1271 updates_scope_--; 1272 DCHECK_GE(updates_scope_, 0); 1273 if (updates_scope_ == 0 && needs_update_) { 1274 needs_update_ = false; 1275 ContentsPreferredSizeChanged(); 1276 } 1277} 1278 1279void AutofillDialogViews::UpdateAccountChooser() { 1280 account_chooser_->Update(); 1281 1282 bool show_loading = delegate_->ShouldShowSpinner(); 1283 if (show_loading != loading_shield_->visible()) { 1284 if (show_loading) { 1285 loading_shield_height_ = std::max(kInitialLoadingShieldHeight, 1286 GetContentsBounds().height()); 1287 ShowDialogInMode(LOADING); 1288 } else { 1289 bool show_sign_in = delegate_->ShouldShowSignInWebView(); 1290 ShowDialogInMode(show_sign_in ? SIGN_IN : DETAIL_INPUT); 1291 } 1292 1293 InvalidateLayout(); 1294 ContentsPreferredSizeChanged(); 1295 } 1296 1297 // Update legal documents for the account. 1298 if (footnote_view_) { 1299 const base::string16 text = delegate_->LegalDocumentsText(); 1300 legal_document_view_->SetText(text); 1301 1302 if (!text.empty()) { 1303 const std::vector<gfx::Range>& link_ranges = 1304 delegate_->LegalDocumentLinks(); 1305 for (size_t i = 0; i < link_ranges.size(); ++i) { 1306 views::StyledLabel::RangeStyleInfo link_range_info = 1307 views::StyledLabel::RangeStyleInfo::CreateForLink(); 1308 link_range_info.disable_line_wrapping = false; 1309 legal_document_view_->AddStyleRange(link_ranges[i], link_range_info); 1310 } 1311 } 1312 1313 footnote_view_->SetVisible(!text.empty()); 1314 ContentsPreferredSizeChanged(); 1315 } 1316 1317 if (GetWidget()) 1318 GetWidget()->UpdateWindowTitle(); 1319} 1320 1321void AutofillDialogViews::UpdateButtonStrip() { 1322 button_strip_extra_view_->SetVisible( 1323 GetDialogButtons() != ui::DIALOG_BUTTON_NONE); 1324 UpdateButtonStripExtraView(); 1325 GetDialogClientView()->UpdateDialogButtons(); 1326 1327 ContentsPreferredSizeChanged(); 1328} 1329 1330void AutofillDialogViews::UpdateOverlay() { 1331 overlay_view_->UpdateState(); 1332 ContentsPreferredSizeChanged(); 1333} 1334 1335void AutofillDialogViews::UpdateDetailArea() { 1336 scrollable_area_->SetVisible(true); 1337 ContentsPreferredSizeChanged(); 1338} 1339 1340void AutofillDialogViews::UpdateForErrors() { 1341 ValidateForm(); 1342} 1343 1344void AutofillDialogViews::UpdateNotificationArea() { 1345 DCHECK(notification_area_); 1346 notification_area_->SetNotifications(delegate_->CurrentNotifications()); 1347 ContentsPreferredSizeChanged(); 1348} 1349 1350void AutofillDialogViews::UpdateSection(DialogSection section) { 1351 UpdateSectionImpl(section, true); 1352} 1353 1354void AutofillDialogViews::UpdateErrorBubble() { 1355 if (!delegate_->ShouldShowErrorBubble()) 1356 HideErrorBubble(); 1357} 1358 1359void AutofillDialogViews::FillSection(DialogSection section, 1360 ServerFieldType originating_type) { 1361 DetailsGroup* group = GroupForSection(section); 1362 // Make sure to overwrite the originating input if it exists. 1363 TextfieldMap::iterator text_mapping = 1364 group->textfields.find(originating_type); 1365 if (text_mapping != group->textfields.end()) 1366 text_mapping->second->SetText(base::string16()); 1367 1368 // If the Autofill data comes from a credit card, make sure to overwrite the 1369 // CC comboboxes (even if they already have something in them). If the 1370 // Autofill data comes from an AutofillProfile, leave the comboboxes alone. 1371 if (section == GetCreditCardSection() && 1372 AutofillType(originating_type).group() == CREDIT_CARD) { 1373 for (ComboboxMap::const_iterator it = group->comboboxes.begin(); 1374 it != group->comboboxes.end(); ++it) { 1375 if (AutofillType(it->first).group() == CREDIT_CARD) 1376 it->second->SetSelectedIndex(it->second->model()->GetDefaultIndex()); 1377 } 1378 } 1379 1380 UpdateSectionImpl(section, false); 1381} 1382 1383void AutofillDialogViews::GetUserInput(DialogSection section, 1384 FieldValueMap* output) { 1385 DetailsGroup* group = GroupForSection(section); 1386 for (TextfieldMap::const_iterator it = group->textfields.begin(); 1387 it != group->textfields.end(); ++it) { 1388 output->insert(std::make_pair(it->first, it->second->GetText())); 1389 } 1390 for (ComboboxMap::const_iterator it = group->comboboxes.begin(); 1391 it != group->comboboxes.end(); ++it) { 1392 output->insert(std::make_pair(it->first, 1393 it->second->model()->GetItemAt(it->second->selected_index()))); 1394 } 1395} 1396 1397base::string16 AutofillDialogViews::GetCvc() { 1398 return GroupForSection(GetCreditCardSection())->suggested_info-> 1399 textfield()->GetText(); 1400} 1401 1402bool AutofillDialogViews::SaveDetailsLocally() { 1403 DCHECK(save_in_chrome_checkbox_->visible()); 1404 return save_in_chrome_checkbox_->checked(); 1405} 1406 1407const content::NavigationController* AutofillDialogViews::ShowSignIn() { 1408 // The initial minimum width and height are set such that the dialog 1409 // won't change size before the page is loaded. 1410 int min_width = GetContentsBounds().width(); 1411 // The height has to include the button strip. 1412 int min_height = GetDialogClientView()->GetContentsBounds().height(); 1413 1414 // TODO(abodenha): We should be able to use the WebContents of the WebView 1415 // to navigate instead of LoadInitialURL. Figure out why it doesn't work. 1416 sign_in_delegate_.reset( 1417 new AutofillDialogSignInDelegate( 1418 this, 1419 sign_in_web_view_->GetWebContents(), 1420 delegate_->GetWebContents(), 1421 gfx::Size(min_width, min_height), GetMaximumSignInViewSize())); 1422 sign_in_web_view_->LoadInitialURL(delegate_->SignInUrl()); 1423 1424 ShowDialogInMode(SIGN_IN); 1425 1426 ContentsPreferredSizeChanged(); 1427 1428 return &sign_in_web_view_->web_contents()->GetController(); 1429} 1430 1431void AutofillDialogViews::HideSignIn() { 1432 sign_in_web_view_->SetWebContents(NULL); 1433 1434 if (delegate_->ShouldShowSpinner()) { 1435 UpdateAccountChooser(); 1436 } else { 1437 ShowDialogInMode(DETAIL_INPUT); 1438 InvalidateLayout(); 1439 } 1440 DCHECK(!sign_in_web_view_->visible()); 1441 1442 ContentsPreferredSizeChanged(); 1443} 1444 1445void AutofillDialogViews::ModelChanged() { 1446 menu_runner_.reset(); 1447 1448 for (DetailGroupMap::const_iterator iter = detail_groups_.begin(); 1449 iter != detail_groups_.end(); ++iter) { 1450 UpdateDetailsGroupState(iter->second); 1451 } 1452} 1453 1454void AutofillDialogViews::OnSignInResize(const gfx::Size& pref_size) { 1455 sign_in_web_view_->SetPreferredSize(pref_size); 1456 ContentsPreferredSizeChanged(); 1457} 1458 1459void AutofillDialogViews::ValidateSection(DialogSection section) { 1460 ValidateGroup(*GroupForSection(section), VALIDATE_EDIT); 1461} 1462 1463gfx::Size AutofillDialogViews::GetPreferredSize() const { 1464 if (preferred_size_.IsEmpty()) 1465 preferred_size_ = CalculatePreferredSize(false); 1466 1467 return preferred_size_; 1468} 1469 1470gfx::Size AutofillDialogViews::GetMinimumSize() const { 1471 return CalculatePreferredSize(true); 1472} 1473 1474void AutofillDialogViews::Layout() { 1475 const gfx::Rect content_bounds = GetContentsBounds(); 1476 if (sign_in_web_view_->visible()) { 1477 sign_in_web_view_->SetBoundsRect(content_bounds); 1478 return; 1479 } 1480 1481 if (loading_shield_->visible()) { 1482 loading_shield_->SetBoundsRect(bounds()); 1483 return; 1484 } 1485 1486 const int x = content_bounds.x(); 1487 const int y = content_bounds.y(); 1488 const int width = content_bounds.width(); 1489 // Layout notification area at top of dialog. 1490 int notification_height = notification_area_->GetHeightForWidth(width); 1491 notification_area_->SetBounds(x, y, width, notification_height); 1492 1493 // The rest (the |scrollable_area_|) takes up whatever's left. 1494 if (scrollable_area_->visible()) { 1495 int scroll_y = y; 1496 if (notification_height > notification_area_->GetInsets().height()) 1497 scroll_y += notification_height + views::kRelatedControlVerticalSpacing; 1498 1499 int scroll_bottom = content_bounds.bottom(); 1500 DCHECK_EQ(scrollable_area_->contents(), details_container_); 1501 details_container_->SizeToPreferredSize(); 1502 details_container_->Layout(); 1503 // TODO(estade): remove this hack. See crbug.com/285996 1504 details_container_->set_ignore_layouts(true); 1505 scrollable_area_->SetBounds(x, scroll_y, width, scroll_bottom - scroll_y); 1506 details_container_->set_ignore_layouts(false); 1507 } 1508 1509 if (error_bubble_) 1510 error_bubble_->UpdatePosition(); 1511} 1512 1513void AutofillDialogViews::OnNativeThemeChanged( 1514 const ui::NativeTheme* theme) { 1515 if (!legal_document_view_) 1516 return; 1517 1518 // NOTE: This color may change because of |auto_color_readability|, set on 1519 // |legal_document_view_|. 1520 views::StyledLabel::RangeStyleInfo default_style; 1521 default_style.color = 1522 theme->GetSystemColor(ui::NativeTheme::kColorId_LabelDisabledColor); 1523 1524 legal_document_view_->SetDefaultStyle(default_style); 1525} 1526 1527ui::ModalType AutofillDialogViews::GetModalType() const { 1528 return ui::MODAL_TYPE_CHILD; 1529} 1530 1531base::string16 AutofillDialogViews::GetWindowTitle() const { 1532 base::string16 title = delegate_->DialogTitle(); 1533 // Hack alert: we don't want the dialog to jiggle when a title is added or 1534 // removed. Setting a non-empty string here keeps the dialog's title bar the 1535 // same size. 1536 return title.empty() ? base::ASCIIToUTF16(" ") : title; 1537} 1538 1539void AutofillDialogViews::WindowClosing() { 1540 focus_manager_->RemoveFocusChangeListener(this); 1541} 1542 1543void AutofillDialogViews::DeleteDelegate() { 1544 window_ = NULL; 1545 // |this| belongs to the controller (|delegate_|). 1546 delegate_->ViewClosed(); 1547} 1548 1549int AutofillDialogViews::GetDialogButtons() const { 1550 return delegate_->GetDialogButtons(); 1551} 1552 1553int AutofillDialogViews::GetDefaultDialogButton() const { 1554 if (GetDialogButtons() & ui::DIALOG_BUTTON_OK) 1555 return ui::DIALOG_BUTTON_OK; 1556 1557 return ui::DIALOG_BUTTON_NONE; 1558} 1559 1560base::string16 AutofillDialogViews::GetDialogButtonLabel( 1561 ui::DialogButton button) const { 1562 return button == ui::DIALOG_BUTTON_OK ? 1563 delegate_->ConfirmButtonText() : delegate_->CancelButtonText(); 1564} 1565 1566bool AutofillDialogViews::ShouldDefaultButtonBeBlue() const { 1567 return true; 1568} 1569 1570bool AutofillDialogViews::IsDialogButtonEnabled(ui::DialogButton button) const { 1571 return delegate_->IsDialogButtonEnabled(button); 1572} 1573 1574views::View* AutofillDialogViews::GetInitiallyFocusedView() { 1575 if (!window_ || !focus_manager_) 1576 return NULL; 1577 1578 if (sign_in_web_view_->visible()) 1579 return sign_in_web_view_; 1580 1581 if (loading_shield_->visible()) 1582 return views::DialogDelegateView::GetInitiallyFocusedView(); 1583 1584 DCHECK(scrollable_area_->visible()); 1585 1586 views::FocusManager* manager = focus_manager_; 1587 for (views::View* next = scrollable_area_; 1588 next; 1589 next = manager->GetNextFocusableView(next, window_, false, true)) { 1590 views::View* input_view = GetAncestralInputView(next); 1591 if (!input_view) 1592 continue; 1593 1594 // If there are no invalid inputs, return the first input found. Otherwise, 1595 // return the first invalid input found. 1596 if (validity_map_.empty() || 1597 validity_map_.find(input_view) != validity_map_.end()) { 1598 return next; 1599 } 1600 } 1601 1602 return views::DialogDelegateView::GetInitiallyFocusedView(); 1603} 1604 1605views::View* AutofillDialogViews::CreateExtraView() { 1606 return button_strip_extra_view_; 1607} 1608 1609views::View* AutofillDialogViews::CreateTitlebarExtraView() { 1610 return account_chooser_; 1611} 1612 1613views::View* AutofillDialogViews::CreateFootnoteView() { 1614 footnote_view_ = new LayoutPropagationView(); 1615 footnote_view_->SetLayoutManager( 1616 new views::BoxLayout(views::BoxLayout::kVertical, 1617 kDialogEdgePadding, 1618 kDialogEdgePadding, 1619 0)); 1620 footnote_view_->SetBorder( 1621 views::Border::CreateSolidSidedBorder(1, 0, 0, 0, kSubtleBorderColor)); 1622 footnote_view_->set_background( 1623 views::Background::CreateSolidBackground(kShadingColor)); 1624 1625 legal_document_view_ = new views::StyledLabel(base::string16(), this); 1626 1627 footnote_view_->AddChildView(legal_document_view_); 1628 footnote_view_->SetVisible(false); 1629 1630 return footnote_view_; 1631} 1632 1633views::View* AutofillDialogViews::CreateOverlayView() { 1634 return overlay_view_; 1635} 1636 1637bool AutofillDialogViews::Cancel() { 1638 return delegate_->OnCancel(); 1639} 1640 1641bool AutofillDialogViews::Accept() { 1642 if (ValidateForm()) 1643 return delegate_->OnAccept(); 1644 1645 // |ValidateForm()| failed; there should be invalid views in |validity_map_|. 1646 DCHECK(!validity_map_.empty()); 1647 FocusInitialView(); 1648 1649 return false; 1650} 1651 1652void AutofillDialogViews::ContentsChanged(views::Textfield* sender, 1653 const base::string16& new_contents) { 1654 InputEditedOrActivated(TypeForTextfield(sender), 1655 sender->GetBoundsInScreen(), 1656 true); 1657 1658 const ExpandingTextfield* expanding = static_cast<ExpandingTextfield*>( 1659 sender->GetAncestorWithClassName(ExpandingTextfield::kViewClassName)); 1660 if (expanding && expanding->needs_layout()) 1661 ContentsPreferredSizeChanged(); 1662} 1663 1664bool AutofillDialogViews::HandleKeyEvent(views::Textfield* sender, 1665 const ui::KeyEvent& key_event) { 1666 ui::KeyEvent copy(key_event); 1667 content::NativeWebKeyboardEvent event(©); 1668 return delegate_->HandleKeyPressEventInInput(event); 1669} 1670 1671bool AutofillDialogViews::HandleMouseEvent(views::Textfield* sender, 1672 const ui::MouseEvent& mouse_event) { 1673 if (mouse_event.IsLeftMouseButton() && sender->HasFocus()) { 1674 InputEditedOrActivated(TypeForTextfield(sender), 1675 sender->GetBoundsInScreen(), 1676 false); 1677 // Show an error bubble if a user clicks on an input that's already focused 1678 // (and invalid). 1679 ShowErrorBubbleForViewIfNecessary(sender); 1680 } 1681 1682 return false; 1683} 1684 1685void AutofillDialogViews::OnWillChangeFocus( 1686 views::View* focused_before, 1687 views::View* focused_now) { 1688 delegate_->FocusMoved(); 1689 HideErrorBubble(); 1690} 1691 1692void AutofillDialogViews::OnDidChangeFocus( 1693 views::View* focused_before, 1694 views::View* focused_now) { 1695 // If user leaves an edit-field, revalidate the group it belongs to. 1696 if (focused_before) { 1697 DetailsGroup* group = GroupForView(focused_before); 1698 if (group && group->container->visible()) 1699 ValidateGroup(*group, VALIDATE_EDIT); 1700 } 1701 1702 // Show an error bubble when the user focuses the input. 1703 if (focused_now) { 1704 focused_now->ScrollRectToVisible(focused_now->GetLocalBounds()); 1705 ShowErrorBubbleForViewIfNecessary(focused_now); 1706 } 1707} 1708 1709void AutofillDialogViews::OnPerformAction(views::Combobox* combobox) { 1710 DialogSection section = GroupForView(combobox)->section; 1711 InputEditedOrActivated(TypeForCombobox(combobox), gfx::Rect(), true); 1712 // NOTE: |combobox| may have been deleted. 1713 ValidateGroup(*GroupForSection(section), VALIDATE_EDIT); 1714 SetEditabilityForSection(section); 1715} 1716 1717void AutofillDialogViews::StyledLabelLinkClicked(const gfx::Range& range, 1718 int event_flags) { 1719 delegate_->LegalDocumentLinkClicked(range); 1720} 1721 1722void AutofillDialogViews::OnMenuButtonClicked(views::View* source, 1723 const gfx::Point& point) { 1724 DCHECK_EQ(kSuggestedButtonClassName, source->GetClassName()); 1725 1726 DetailsGroup* group = NULL; 1727 for (DetailGroupMap::iterator iter = detail_groups_.begin(); 1728 iter != detail_groups_.end(); ++iter) { 1729 if (source == iter->second.suggested_button) { 1730 group = &iter->second; 1731 break; 1732 } 1733 } 1734 DCHECK(group); 1735 1736 if (!group->suggested_button->visible()) 1737 return; 1738 1739 menu_runner_.reset( 1740 new views::MenuRunner(delegate_->MenuModelForSection(group->section), 0)); 1741 1742 group->container->SetActive(true); 1743 views::Button::ButtonState state = group->suggested_button->state(); 1744 group->suggested_button->SetState(views::Button::STATE_PRESSED); 1745 1746 gfx::Rect screen_bounds = source->GetBoundsInScreen(); 1747 screen_bounds.Inset(source->GetInsets()); 1748 if (menu_runner_->RunMenuAt(source->GetWidget(), 1749 NULL, 1750 screen_bounds, 1751 views::MENU_ANCHOR_TOPRIGHT, 1752 ui::MENU_SOURCE_NONE) == 1753 views::MenuRunner::MENU_DELETED) { 1754 return; 1755 } 1756 1757 group->container->SetActive(false); 1758 group->suggested_button->SetState(state); 1759} 1760 1761gfx::Size AutofillDialogViews::CalculatePreferredSize( 1762 bool get_minimum_size) const { 1763 gfx::Insets insets = GetInsets(); 1764 gfx::Size scroll_size = scrollable_area_->contents()->GetPreferredSize(); 1765 // The width is always set by the scroll area. 1766 const int width = scroll_size.width(); 1767 1768 if (sign_in_web_view_->visible()) { 1769 const gfx::Size size = static_cast<views::View*>(sign_in_web_view_)-> 1770 GetPreferredSize(); 1771 return gfx::Size(width + insets.width(), size.height() + insets.height()); 1772 } 1773 1774 if (overlay_view_->visible()) { 1775 const int height = overlay_view_->GetHeightForContentsForWidth(width); 1776 if (height != 0) 1777 return gfx::Size(width + insets.width(), height + insets.height()); 1778 } 1779 1780 if (loading_shield_->visible()) { 1781 return gfx::Size(width + insets.width(), 1782 loading_shield_height_ + insets.height()); 1783 } 1784 1785 int height = 0; 1786 const int notification_height = notification_area_->GetHeightForWidth(width); 1787 if (notification_height > notification_area_->GetInsets().height()) 1788 height += notification_height + views::kRelatedControlVerticalSpacing; 1789 1790 if (scrollable_area_->visible()) 1791 height += get_minimum_size ? kMinimumContentsHeight : scroll_size.height(); 1792 1793 return gfx::Size(width + insets.width(), height + insets.height()); 1794} 1795 1796gfx::Size AutofillDialogViews::GetMinimumSignInViewSize() const { 1797 return gfx::Size(GetDialogClientView()->size().width() - GetInsets().width(), 1798 kMinimumContentsHeight); 1799} 1800 1801gfx::Size AutofillDialogViews::GetMaximumSignInViewSize() const { 1802 web_modal::WebContentsModalDialogHost* dialog_host = 1803 web_modal::WebContentsModalDialogManager::FromWebContents( 1804 delegate_->GetWebContents())->delegate()-> 1805 GetWebContentsModalDialogHost(); 1806 1807 // Inset the maximum dialog height to get the maximum content height. 1808 int height = dialog_host->GetMaximumDialogSize().height(); 1809 const int non_client_height = GetWidget()->non_client_view()->height(); 1810 const int client_height = GetWidget()->client_view()->height(); 1811 // TODO(msw): Resolve the 12 pixel discrepancy; is that the bubble border? 1812 height -= non_client_height - client_height - 12; 1813 height = std::max(height, kMinimumContentsHeight); 1814 1815 // The dialog's width never changes. 1816 const int width = GetDialogClientView()->size().width() - GetInsets().width(); 1817 return gfx::Size(width, height); 1818} 1819 1820DialogSection AutofillDialogViews::GetCreditCardSection() const { 1821 if (delegate_->SectionIsActive(SECTION_CC)) 1822 return SECTION_CC; 1823 1824 DCHECK(delegate_->SectionIsActive(SECTION_CC_BILLING)); 1825 return SECTION_CC_BILLING; 1826} 1827 1828void AutofillDialogViews::InitChildViews() { 1829 button_strip_extra_view_ = new LayoutPropagationView(); 1830 button_strip_extra_view_->SetLayoutManager( 1831 new views::BoxLayout(views::BoxLayout::kHorizontal, 0, 0, 0)); 1832 1833 save_in_chrome_checkbox_container_ = new views::View(); 1834 save_in_chrome_checkbox_container_->SetLayoutManager( 1835 new views::BoxLayout(views::BoxLayout::kHorizontal, 0, 0, 7)); 1836 button_strip_extra_view_->AddChildView(save_in_chrome_checkbox_container_); 1837 1838 save_in_chrome_checkbox_ = 1839 new views::Checkbox(delegate_->SaveLocallyText()); 1840 save_in_chrome_checkbox_->SetChecked(delegate_->ShouldSaveInChrome()); 1841 save_in_chrome_checkbox_container_->AddChildView(save_in_chrome_checkbox_); 1842 1843 save_in_chrome_checkbox_container_->AddChildView( 1844 new TooltipIcon(delegate_->SaveLocallyTooltip())); 1845 1846 button_strip_image_ = new views::ImageView(); 1847 button_strip_extra_view_->AddChildView(button_strip_image_); 1848 1849 account_chooser_ = new AccountChooser(delegate_); 1850 notification_area_ = new NotificationArea(delegate_); 1851 notification_area_->set_arrow_centering_anchor(account_chooser_->AsWeakPtr()); 1852 AddChildView(notification_area_); 1853 1854 scrollable_area_ = new views::ScrollView(); 1855 scrollable_area_->set_hide_horizontal_scrollbar(true); 1856 scrollable_area_->SetContents(CreateDetailsContainer()); 1857 AddChildView(scrollable_area_); 1858 1859 loading_shield_ = new LoadingAnimationView(delegate_->SpinnerText()); 1860 AddChildView(loading_shield_); 1861 1862 sign_in_web_view_ = new views::WebView(delegate_->profile()); 1863 AddChildView(sign_in_web_view_); 1864 1865 overlay_view_ = new OverlayView(delegate_); 1866 overlay_view_->SetVisible(false); 1867} 1868 1869views::View* AutofillDialogViews::CreateDetailsContainer() { 1870 details_container_ = new DetailsContainerView( 1871 base::Bind(&AutofillDialogViews::DetailsContainerBoundsChanged, 1872 base::Unretained(this))); 1873 1874 // A box layout is used because it respects widget visibility. 1875 details_container_->SetLayoutManager( 1876 new views::BoxLayout(views::BoxLayout::kVertical, 0, 0, 0)); 1877 for (DetailGroupMap::iterator iter = detail_groups_.begin(); 1878 iter != detail_groups_.end(); ++iter) { 1879 CreateDetailsSection(iter->second.section); 1880 details_container_->AddChildView(iter->second.container); 1881 } 1882 1883 return details_container_; 1884} 1885 1886void AutofillDialogViews::CreateDetailsSection(DialogSection section) { 1887 // Inputs container (manual inputs + combobox). 1888 views::View* inputs_container = CreateInputsContainer(section); 1889 1890 DetailsGroup* group = GroupForSection(section); 1891 // Container (holds label + inputs). 1892 group->container = new SectionContainer(delegate_->LabelForSection(section), 1893 inputs_container, 1894 group->suggested_button); 1895 DCHECK(group->suggested_button->parent()); 1896 UpdateDetailsGroupState(*group); 1897} 1898 1899views::View* AutofillDialogViews::CreateInputsContainer(DialogSection section) { 1900 // The |info_view| holds |manual_inputs| and |suggested_info|, allowing the 1901 // dialog to toggle which is shown. 1902 views::View* info_view = new views::View(); 1903 info_view->SetLayoutManager( 1904 new views::BoxLayout(views::BoxLayout::kVertical, 0, 0, 0)); 1905 1906 DetailsGroup* group = GroupForSection(section); 1907 group->manual_input = new views::View(); 1908 InitInputsView(section); 1909 info_view->AddChildView(group->manual_input); 1910 1911 group->suggested_info = new SuggestionView(this); 1912 info_view->AddChildView(group->suggested_info); 1913 1914 // TODO(estade): It might be slightly more OO if this button were created 1915 // and listened to by the section container. 1916 group->suggested_button = new SuggestedButton(this); 1917 1918 return info_view; 1919} 1920 1921// TODO(estade): we should be using Chrome-style constrained window padding 1922// values. 1923void AutofillDialogViews::InitInputsView(DialogSection section) { 1924 DetailsGroup* group = GroupForSection(section); 1925 EraseInvalidViewsInGroup(group); 1926 1927 TextfieldMap* textfields = &group->textfields; 1928 textfields->clear(); 1929 1930 ComboboxMap* comboboxes = &group->comboboxes; 1931 comboboxes->clear(); 1932 1933 views::View* view = group->manual_input; 1934 view->RemoveAllChildViews(true); 1935 1936 views::GridLayout* layout = new views::GridLayout(view); 1937 view->SetLayoutManager(layout); 1938 1939 int column_set_id = 0; 1940 const DetailInputs& inputs = delegate_->RequestedFieldsForSection(section); 1941 for (DetailInputs::const_iterator it = inputs.begin(); 1942 it != inputs.end(); ++it) { 1943 const DetailInput& input = *it; 1944 1945 ui::ComboboxModel* input_model = 1946 delegate_->ComboboxModelForAutofillType(input.type); 1947 scoped_ptr<views::View> view_to_add; 1948 if (input_model) { 1949 views::Combobox* combobox = new views::Combobox(input_model); 1950 combobox->set_listener(this); 1951 comboboxes->insert(std::make_pair(input.type, combobox)); 1952 SelectComboboxValueOrSetToDefault(combobox, input.initial_value); 1953 view_to_add.reset(combobox); 1954 } else { 1955 ExpandingTextfield* field = new ExpandingTextfield(input.initial_value, 1956 input.placeholder_text, 1957 input.IsMultiline(), 1958 this); 1959 textfields->insert(std::make_pair(input.type, field)); 1960 view_to_add.reset(field); 1961 } 1962 1963 if (input.length == DetailInput::NONE) { 1964 other_owned_views_.push_back(view_to_add.release()); 1965 continue; 1966 } 1967 1968 if (input.length == DetailInput::LONG) 1969 ++column_set_id; 1970 1971 views::ColumnSet* column_set = layout->GetColumnSet(column_set_id); 1972 if (!column_set) { 1973 // Create a new column set and row. 1974 column_set = layout->AddColumnSet(column_set_id); 1975 if (it != inputs.begin()) 1976 layout->AddPaddingRow(0, kManualInputRowPadding); 1977 layout->StartRow(0, column_set_id); 1978 } else { 1979 // Add a new column to existing row. 1980 column_set->AddPaddingColumn(0, views::kRelatedControlHorizontalSpacing); 1981 // Must explicitly skip the padding column since we've already started 1982 // adding views. 1983 layout->SkipColumns(1); 1984 } 1985 1986 float expand = input.expand_weight; 1987 column_set->AddColumn(views::GridLayout::FILL, 1988 views::GridLayout::FILL, 1989 expand ? expand : 1.0, 1990 views::GridLayout::USE_PREF, 1991 0, 1992 0); 1993 1994 // This is the same as AddView(view_to_add), except that 1 is used for the 1995 // view's preferred width. Thus the width of the column completely depends 1996 // on |expand|. 1997 layout->AddView(view_to_add.release(), 1, 1, 1998 views::GridLayout::FILL, views::GridLayout::FILL, 1999 1, 0); 2000 2001 if (input.length == DetailInput::LONG || 2002 input.length == DetailInput::SHORT_EOL) { 2003 ++column_set_id; 2004 } 2005 } 2006 2007 SetIconsForSection(section); 2008} 2009 2010void AutofillDialogViews::ShowDialogInMode(DialogMode dialog_mode) { 2011 loading_shield_->SetVisible(dialog_mode == LOADING); 2012 sign_in_web_view_->SetVisible(dialog_mode == SIGN_IN); 2013 notification_area_->SetVisible(dialog_mode == DETAIL_INPUT); 2014 scrollable_area_->SetVisible(dialog_mode == DETAIL_INPUT); 2015 FocusInitialView(); 2016} 2017 2018void AutofillDialogViews::UpdateSectionImpl( 2019 DialogSection section, 2020 bool clobber_inputs) { 2021 DetailsGroup* group = GroupForSection(section); 2022 2023 if (clobber_inputs) { 2024 ServerFieldType type = UNKNOWN_TYPE; 2025 views::View* focused = GetFocusManager()->GetFocusedView(); 2026 if (focused && group->container->Contains(focused)) { 2027 // Remember which view was focused before the inputs are clobbered. 2028 if (focused->GetClassName() == ExpandingTextfield::kViewClassName) 2029 type = TypeForTextfield(focused); 2030 else if (focused->GetClassName() == views::Combobox::kViewClassName) 2031 type = TypeForCombobox(static_cast<views::Combobox*>(focused)); 2032 } 2033 2034 InitInputsView(section); 2035 2036 if (type != UNKNOWN_TYPE) { 2037 // Restore the focus to the input with the previous type (e.g. country). 2038 views::View* to_focus = TextfieldForType(type); 2039 if (!to_focus) to_focus = ComboboxForType(type); 2040 if (to_focus) 2041 to_focus->RequestFocus(); 2042 } 2043 } else { 2044 const DetailInputs& updated_inputs = 2045 delegate_->RequestedFieldsForSection(section); 2046 2047 for (DetailInputs::const_iterator iter = updated_inputs.begin(); 2048 iter != updated_inputs.end(); ++iter) { 2049 const DetailInput& input = *iter; 2050 2051 TextfieldMap::iterator text_mapping = group->textfields.find(input.type); 2052 if (text_mapping != group->textfields.end()) { 2053 ExpandingTextfield* textfield = text_mapping->second; 2054 if (textfield->GetText().empty()) 2055 textfield->SetText(input.initial_value); 2056 } 2057 2058 ComboboxMap::iterator combo_mapping = group->comboboxes.find(input.type); 2059 if (combo_mapping != group->comboboxes.end()) { 2060 views::Combobox* combobox = combo_mapping->second; 2061 if (combobox->selected_index() == combobox->model()->GetDefaultIndex()) 2062 SelectComboboxValueOrSetToDefault(combobox, input.initial_value); 2063 } 2064 } 2065 2066 SetIconsForSection(section); 2067 } 2068 2069 SetEditabilityForSection(section); 2070 UpdateDetailsGroupState(*group); 2071} 2072 2073void AutofillDialogViews::UpdateDetailsGroupState(const DetailsGroup& group) { 2074 const SuggestionState& suggestion_state = 2075 delegate_->SuggestionStateForSection(group.section); 2076 group.suggested_info->SetState(suggestion_state); 2077 group.manual_input->SetVisible(!suggestion_state.visible); 2078 2079 UpdateButtonStripExtraView(); 2080 2081 const bool has_menu = !!delegate_->MenuModelForSection(group.section); 2082 2083 if (group.suggested_button) 2084 group.suggested_button->SetVisible(has_menu); 2085 2086 if (group.container) { 2087 group.container->SetForwardMouseEvents( 2088 has_menu && suggestion_state.visible); 2089 group.container->SetVisible(delegate_->SectionIsActive(group.section)); 2090 if (group.container->visible()) 2091 ValidateGroup(group, VALIDATE_EDIT); 2092 } 2093 2094 ContentsPreferredSizeChanged(); 2095} 2096 2097void AutofillDialogViews::FocusInitialView() { 2098 views::View* to_focus = GetInitiallyFocusedView(); 2099 if (to_focus && !to_focus->HasFocus()) 2100 to_focus->RequestFocus(); 2101} 2102 2103template<class T> 2104void AutofillDialogViews::SetValidityForInput( 2105 T* input, 2106 const base::string16& message) { 2107 bool invalid = !message.empty(); 2108 input->SetInvalid(invalid); 2109 2110 if (invalid) { 2111 validity_map_[input] = message; 2112 } else { 2113 validity_map_.erase(input); 2114 2115 if (error_bubble_ && 2116 error_bubble_->anchor()->GetAncestorWithClassName( 2117 input->GetClassName()) == input) { 2118 validity_map_.erase(input); 2119 HideErrorBubble(); 2120 } 2121 } 2122} 2123 2124void AutofillDialogViews::ShowErrorBubbleForViewIfNecessary(views::View* view) { 2125 if (!view->GetWidget()) 2126 return; 2127 2128 if (!delegate_->ShouldShowErrorBubble()) { 2129 DCHECK(!error_bubble_); 2130 return; 2131 } 2132 2133 if (view->GetClassName() == DecoratedTextfield::kViewClassName && 2134 !static_cast<DecoratedTextfield*>(view)->invalid()) { 2135 return; 2136 } 2137 2138 views::View* input_view = GetAncestralInputView(view); 2139 std::map<views::View*, base::string16>::iterator error_message = 2140 validity_map_.find(input_view); 2141 if (error_message != validity_map_.end()) { 2142 input_view->ScrollRectToVisible(input_view->GetLocalBounds()); 2143 2144 if (!error_bubble_ || error_bubble_->anchor() != view) { 2145 HideErrorBubble(); 2146 error_bubble_ = new InfoBubble(view, error_message->second); 2147 error_bubble_->set_align_to_anchor_edge(true); 2148 error_bubble_->set_preferred_width( 2149 (kSectionContainerWidth - views::kRelatedControlVerticalSpacing) / 2); 2150 bool show_above = view->GetClassName() == views::Combobox::kViewClassName; 2151 error_bubble_->set_show_above_anchor(show_above); 2152 error_bubble_->Show(); 2153 observer_.Add(error_bubble_->GetWidget()); 2154 } 2155 } 2156} 2157 2158void AutofillDialogViews::HideErrorBubble() { 2159 if (error_bubble_) 2160 error_bubble_->Hide(); 2161} 2162 2163void AutofillDialogViews::MarkInputsInvalid( 2164 DialogSection section, 2165 const ValidityMessages& messages, 2166 bool overwrite_unsure) { 2167 DetailsGroup* group = GroupForSection(section); 2168 DCHECK(group->container->visible()); 2169 2170 if (group->manual_input->visible()) { 2171 for (TextfieldMap::const_iterator iter = group->textfields.begin(); 2172 iter != group->textfields.end(); ++iter) { 2173 const ValidityMessage& message = 2174 messages.GetMessageOrDefault(iter->first); 2175 if (overwrite_unsure || message.sure) 2176 SetValidityForInput(iter->second, message.text); 2177 } 2178 for (ComboboxMap::const_iterator iter = group->comboboxes.begin(); 2179 iter != group->comboboxes.end(); ++iter) { 2180 const ValidityMessage& message = 2181 messages.GetMessageOrDefault(iter->first); 2182 if (overwrite_unsure || message.sure) 2183 SetValidityForInput(iter->second, message.text); 2184 } 2185 } else { 2186 EraseInvalidViewsInGroup(group); 2187 2188 if (section == GetCreditCardSection()) { 2189 // Special case CVC as it's not part of |group->manual_input|. 2190 const ValidityMessage& message = 2191 messages.GetMessageOrDefault(CREDIT_CARD_VERIFICATION_CODE); 2192 if (overwrite_unsure || message.sure) { 2193 SetValidityForInput(group->suggested_info->textfield(), message.text); 2194 } 2195 } 2196 } 2197} 2198 2199bool AutofillDialogViews::ValidateGroup(const DetailsGroup& group, 2200 ValidationType validation_type) { 2201 DCHECK(group.container->visible()); 2202 2203 FieldValueMap detail_outputs; 2204 2205 if (group.manual_input->visible()) { 2206 for (TextfieldMap::const_iterator iter = group.textfields.begin(); 2207 iter != group.textfields.end(); ++iter) { 2208 if (!iter->second->editable()) 2209 continue; 2210 2211 detail_outputs[iter->first] = iter->second->GetText(); 2212 } 2213 for (ComboboxMap::const_iterator iter = group.comboboxes.begin(); 2214 iter != group.comboboxes.end(); ++iter) { 2215 if (!iter->second->enabled()) 2216 continue; 2217 2218 views::Combobox* combobox = iter->second; 2219 base::string16 item = 2220 combobox->model()->GetItemAt(combobox->selected_index()); 2221 detail_outputs[iter->first] = item; 2222 } 2223 } else if (group.section == GetCreditCardSection()) { 2224 ExpandingTextfield* cvc = group.suggested_info->textfield(); 2225 if (cvc->visible()) 2226 detail_outputs[CREDIT_CARD_VERIFICATION_CODE] = cvc->GetText(); 2227 } 2228 2229 ValidityMessages validity = delegate_->InputsAreValid(group.section, 2230 detail_outputs); 2231 MarkInputsInvalid(group.section, validity, validation_type == VALIDATE_FINAL); 2232 2233 // If there are any validation errors, sure or unsure, the group is invalid. 2234 return !validity.HasErrors(); 2235} 2236 2237bool AutofillDialogViews::ValidateForm() { 2238 bool all_valid = true; 2239 validity_map_.clear(); 2240 2241 for (DetailGroupMap::iterator iter = detail_groups_.begin(); 2242 iter != detail_groups_.end(); ++iter) { 2243 const DetailsGroup& group = iter->second; 2244 if (!group.container->visible()) 2245 continue; 2246 2247 if (!ValidateGroup(group, VALIDATE_FINAL)) 2248 all_valid = false; 2249 } 2250 2251 return all_valid; 2252} 2253 2254void AutofillDialogViews::InputEditedOrActivated(ServerFieldType type, 2255 const gfx::Rect& bounds, 2256 bool was_edit) { 2257 DCHECK_NE(UNKNOWN_TYPE, type); 2258 2259 ExpandingTextfield* textfield = TextfieldForType(type); 2260 views::Combobox* combobox = ComboboxForType(type); 2261 2262 // Both views may be NULL if the event comes from an inactive section, which 2263 // may occur when using an IME. 2264 if (!combobox && !textfield) 2265 return; 2266 2267 DCHECK_NE(!!combobox, !!textfield); 2268 DetailsGroup* group = textfield ? GroupForView(textfield) : 2269 GroupForView(combobox); 2270 base::string16 text = textfield ? 2271 textfield->GetText() : 2272 combobox->model()->GetItemAt(combobox->selected_index()); 2273 DCHECK(group); 2274 2275 delegate_->UserEditedOrActivatedInput(group->section, 2276 type, 2277 GetWidget()->GetNativeView(), 2278 bounds, 2279 text, 2280 was_edit); 2281 2282 // If the field is a textfield and is invalid, check if the text is now valid. 2283 // Many fields (i.e. CC#) are invalid for most of the duration of editing, 2284 // so flagging them as invalid prematurely is not helpful. However, 2285 // correcting a minor mistake (i.e. a wrong CC digit) should immediately 2286 // result in validation - positive user feedback. 2287 if (textfield && textfield->invalid() && was_edit) { 2288 SetValidityForInput( 2289 textfield, 2290 delegate_->InputValidityMessage( 2291 group->section, type, textfield->GetText())); 2292 2293 // If the field transitioned from invalid to valid, re-validate the group, 2294 // since inter-field checks become meaningful with valid fields. 2295 if (!textfield->invalid()) 2296 ValidateGroup(*group, VALIDATE_EDIT); 2297 } 2298 2299 if (delegate_->FieldControlsIcons(type)) 2300 SetIconsForSection(group->section); 2301 2302 SetEditabilityForSection(group->section); 2303} 2304 2305void AutofillDialogViews::UpdateButtonStripExtraView() { 2306 save_in_chrome_checkbox_container_->SetVisible( 2307 delegate_->ShouldOfferToSaveInChrome()); 2308 2309 gfx::Image image = delegate_->ButtonStripImage(); 2310 button_strip_image_->SetVisible(!image.IsEmpty()); 2311 button_strip_image_->SetImage(image.AsImageSkia()); 2312} 2313 2314void AutofillDialogViews::ContentsPreferredSizeChanged() { 2315 if (updates_scope_ != 0) { 2316 needs_update_ = true; 2317 return; 2318 } 2319 2320 preferred_size_ = gfx::Size(); 2321 2322 if (GetWidget() && delegate_ && delegate_->GetWebContents()) { 2323 UpdateWebContentsModalDialogPosition( 2324 GetWidget(), 2325 web_modal::WebContentsModalDialogManager::FromWebContents( 2326 delegate_->GetWebContents())->delegate()-> 2327 GetWebContentsModalDialogHost()); 2328 SetBoundsRect(bounds()); 2329 } 2330} 2331 2332AutofillDialogViews::DetailsGroup* AutofillDialogViews::GroupForSection( 2333 DialogSection section) { 2334 return &detail_groups_.find(section)->second; 2335} 2336 2337AutofillDialogViews::DetailsGroup* AutofillDialogViews::GroupForView( 2338 views::View* view) { 2339 DCHECK(view); 2340 2341 views::View* input_view = GetAncestralInputView(view); 2342 if (!input_view) 2343 return NULL; 2344 2345 for (DetailGroupMap::iterator iter = detail_groups_.begin(); 2346 iter != detail_groups_.end(); ++iter) { 2347 DetailsGroup* group = &iter->second; 2348 if (input_view->parent() == group->manual_input) 2349 return group; 2350 2351 // Textfields need to check a second case, since they can be suggested 2352 // inputs instead of directly editable inputs. Those are accessed via 2353 // |suggested_info|. 2354 if (input_view == group->suggested_info->textfield()) { 2355 return group; 2356 } 2357 } 2358 2359 return NULL; 2360} 2361 2362void AutofillDialogViews::EraseInvalidViewsInGroup(const DetailsGroup* group) { 2363 std::map<views::View*, base::string16>::iterator it = validity_map_.begin(); 2364 while (it != validity_map_.end()) { 2365 if (GroupForView(it->first) == group) 2366 validity_map_.erase(it++); 2367 else 2368 ++it; 2369 } 2370} 2371 2372ExpandingTextfield* AutofillDialogViews::TextfieldForType( 2373 ServerFieldType type) { 2374 if (type == CREDIT_CARD_VERIFICATION_CODE) { 2375 DetailsGroup* group = GroupForSection(GetCreditCardSection()); 2376 if (!group->manual_input->visible()) 2377 return group->suggested_info->textfield(); 2378 } 2379 2380 for (DetailGroupMap::iterator iter = detail_groups_.begin(); 2381 iter != detail_groups_.end(); ++iter) { 2382 const DetailsGroup& group = iter->second; 2383 if (!delegate_->SectionIsActive(group.section)) 2384 continue; 2385 2386 TextfieldMap::const_iterator text_mapping = group.textfields.find(type); 2387 if (text_mapping != group.textfields.end()) 2388 return text_mapping->second; 2389 } 2390 2391 return NULL; 2392} 2393 2394ServerFieldType AutofillDialogViews::TypeForTextfield( 2395 const views::View* textfield) { 2396 const views::View* expanding = 2397 textfield->GetAncestorWithClassName(ExpandingTextfield::kViewClassName); 2398 2399 DetailsGroup* cc_group = GroupForSection(GetCreditCardSection()); 2400 if (expanding == cc_group->suggested_info->textfield()) 2401 return CREDIT_CARD_VERIFICATION_CODE; 2402 2403 for (DetailGroupMap::const_iterator it = detail_groups_.begin(); 2404 it != detail_groups_.end(); ++it) { 2405 if (!delegate_->SectionIsActive(it->second.section)) 2406 continue; 2407 2408 for (TextfieldMap::const_iterator text_it = it->second.textfields.begin(); 2409 text_it != it->second.textfields.end(); ++text_it) { 2410 if (expanding == text_it->second) 2411 return text_it->first; 2412 } 2413 } 2414 2415 return UNKNOWN_TYPE; 2416} 2417 2418views::Combobox* AutofillDialogViews::ComboboxForType( 2419 ServerFieldType type) { 2420 for (DetailGroupMap::iterator iter = detail_groups_.begin(); 2421 iter != detail_groups_.end(); ++iter) { 2422 const DetailsGroup& group = iter->second; 2423 if (!delegate_->SectionIsActive(group.section)) 2424 continue; 2425 2426 ComboboxMap::const_iterator combo_mapping = group.comboboxes.find(type); 2427 if (combo_mapping != group.comboboxes.end()) 2428 return combo_mapping->second; 2429 } 2430 2431 return NULL; 2432} 2433 2434ServerFieldType AutofillDialogViews::TypeForCombobox( 2435 const views::Combobox* combobox) const { 2436 for (DetailGroupMap::const_iterator it = detail_groups_.begin(); 2437 it != detail_groups_.end(); ++it) { 2438 const DetailsGroup& group = it->second; 2439 if (!delegate_->SectionIsActive(group.section)) 2440 continue; 2441 2442 for (ComboboxMap::const_iterator combo_it = group.comboboxes.begin(); 2443 combo_it != group.comboboxes.end(); ++combo_it) { 2444 if (combo_it->second == combobox) 2445 return combo_it->first; 2446 } 2447 } 2448 2449 return UNKNOWN_TYPE; 2450} 2451 2452void AutofillDialogViews::DetailsContainerBoundsChanged() { 2453 if (error_bubble_) 2454 error_bubble_->UpdatePosition(); 2455} 2456 2457void AutofillDialogViews::SetIconsForSection(DialogSection section) { 2458 FieldValueMap user_input; 2459 GetUserInput(section, &user_input); 2460 FieldIconMap field_icons = delegate_->IconsForFields(user_input); 2461 TextfieldMap* textfields = &GroupForSection(section)->textfields; 2462 for (TextfieldMap::const_iterator textfield_it = textfields->begin(); 2463 textfield_it != textfields->end(); 2464 ++textfield_it) { 2465 ServerFieldType field_type = textfield_it->first; 2466 FieldIconMap::const_iterator field_icon_it = field_icons.find(field_type); 2467 ExpandingTextfield* textfield = textfield_it->second; 2468 if (field_icon_it != field_icons.end()) 2469 textfield->SetIcon(field_icon_it->second); 2470 else 2471 textfield->SetTooltipIcon(delegate_->TooltipForField(field_type)); 2472 } 2473} 2474 2475void AutofillDialogViews::SetEditabilityForSection(DialogSection section) { 2476 const DetailInputs& inputs = 2477 delegate_->RequestedFieldsForSection(section); 2478 DetailsGroup* group = GroupForSection(section); 2479 2480 for (DetailInputs::const_iterator iter = inputs.begin(); 2481 iter != inputs.end(); ++iter) { 2482 const DetailInput& input = *iter; 2483 bool editable = delegate_->InputIsEditable(input, section); 2484 2485 TextfieldMap::iterator text_mapping = group->textfields.find(input.type); 2486 if (text_mapping != group->textfields.end()) { 2487 ExpandingTextfield* textfield= text_mapping->second; 2488 textfield->SetEditable(editable); 2489 continue; 2490 } 2491 2492 ComboboxMap::iterator combo_mapping = group->comboboxes.find(input.type); 2493 if (combo_mapping != group->comboboxes.end()) { 2494 views::Combobox* combobox = combo_mapping->second; 2495 combobox->SetEnabled(editable); 2496 } 2497 } 2498} 2499 2500void AutofillDialogViews::NonClientMousePressed() { 2501 delegate_->FocusMoved(); 2502} 2503 2504AutofillDialogViews::DetailsGroup::DetailsGroup(DialogSection section) 2505 : section(section), 2506 container(NULL), 2507 manual_input(NULL), 2508 suggested_info(NULL), 2509 suggested_button(NULL) {} 2510 2511AutofillDialogViews::DetailsGroup::~DetailsGroup() {} 2512 2513} // namespace autofill 2514