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