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