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