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