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