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