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