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