autofill_dialog_views.cc revision 90dce4d38c5ff5333bea97d859d4e484e27edf0c
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/utf_string_conversions.h" 11#include "chrome/browser/profiles/profile.h" 12#include "chrome/browser/ui/autofill/autofill_dialog_controller.h" 13#include "chrome/browser/ui/autofill/autofill_dialog_sign_in_delegate.h" 14#include "chrome/browser/ui/views/constrained_window_views.h" 15#include "components/autofill/browser/autofill_type.h" 16#include "components/autofill/browser/wallet/wallet_service_url.h" 17#include "components/web_modal/web_contents_modal_dialog_manager.h" 18#include "components/web_modal/web_contents_modal_dialog_manager_delegate.h" 19#include "content/public/browser/native_web_keyboard_event.h" 20#include "content/public/browser/navigation_controller.h" 21#include "content/public/browser/web_contents.h" 22#include "content/public/browser/web_contents_view.h" 23#include "grit/theme_resources.h" 24#include "grit/ui_resources.h" 25#include "third_party/skia/include/core/SkColor.h" 26#include "ui/base/l10n/l10n_util.h" 27#include "ui/base/models/combobox_model.h" 28#include "ui/base/models/menu_model.h" 29#include "ui/base/resource/resource_bundle.h" 30#include "ui/gfx/canvas.h" 31#include "ui/views/background.h" 32#include "ui/views/border.h" 33#include "ui/views/controls/button/checkbox.h" 34#include "ui/views/controls/button/image_button.h" 35#include "ui/views/controls/button/label_button.h" 36#include "ui/views/controls/button/label_button_border.h" 37#include "ui/views/controls/combobox/combobox.h" 38#include "ui/views/controls/image_view.h" 39#include "ui/views/controls/label.h" 40#include "ui/views/controls/link.h" 41#include "ui/views/controls/menu/menu_runner.h" 42#include "ui/views/controls/separator.h" 43#include "ui/views/controls/styled_label.h" 44#include "ui/views/controls/textfield/textfield.h" 45#include "ui/views/controls/webview/webview.h" 46#include "ui/views/layout/box_layout.h" 47#include "ui/views/layout/fill_layout.h" 48#include "ui/views/layout/grid_layout.h" 49#include "ui/views/layout/layout_constants.h" 50#include "ui/views/widget/widget.h" 51#include "ui/views/window/dialog_client_view.h" 52 53using web_modal::WebContentsModalDialogManager; 54 55namespace autofill { 56 57namespace { 58 59// The minimum useful height of the contents area of the dialog. 60const int kMinimumContentsHeight = 100; 61 62// Horizontal padding between text and other elements (in pixels). 63const int kAroundTextPadding = 4; 64 65// Size of the triangular mark that indicates an invalid textfield (in pixels). 66const size_t kDogEarSize = 10; 67 68// The space between the edges of a notification bar and the text within (in 69// pixels). 70const size_t kNotificationPadding = 14; 71 72// Vertical padding above and below each detail section (in pixels). 73const size_t kDetailSectionInset = 10; 74 75const size_t kAutocheckoutProgressBarWidth = 300; 76const size_t kAutocheckoutProgressBarHeight = 11; 77 78const size_t kArrowHeight = 7; 79const size_t kArrowWidth = 2 * kArrowHeight; 80 81// The padding around the edges of the legal documents text, in pixels. 82const size_t kLegalDocPadding = 20; 83 84// Slight shading for mouse hover and legal document background. 85SkColor kShadingColor = SkColorSetARGB(7, 0, 0, 0); 86 87// A border color for the legal document view. 88SkColor kSubtleBorderColor = SkColorSetARGB(10, 0, 0, 0); 89 90// The top padding, in pixels, for the suggestions menu dropdown arrows. 91const size_t kMenuButtonTopOffset = 5; 92 93// The side padding, in pixels, for the suggestions menu dropdown arrows. 94const size_t kMenuButtonHorizontalPadding = 20; 95 96const char kDecoratedTextfieldClassName[] = "autofill/DecoratedTextfield"; 97const char kNotificationAreaClassName[] = "autofill/NotificationArea"; 98 99views::Border* CreateLabelAlignmentBorder() { 100 // TODO(estade): this should be made to match the native textfield top 101 // inset. It's hard to get at, so for now it's hard-coded. 102 return views::Border::CreateEmptyBorder(4, 0, 0, 0); 103} 104 105// Returns a label that describes a details section. 106views::Label* CreateDetailsSectionLabel(const string16& text) { 107 views::Label* label = new views::Label(text); 108 label->SetHorizontalAlignment(gfx::ALIGN_RIGHT); 109 label->SetFont(label->font().DeriveFont(0, gfx::Font::BOLD)); 110 label->set_border(CreateLabelAlignmentBorder()); 111 return label; 112} 113 114// Draws an arrow at the top of |canvas| pointing to |tip_x|. 115void DrawArrow(gfx::Canvas* canvas, int tip_x, const SkColor& color) { 116 const int arrow_half_width = kArrowWidth / 2.0f; 117 const int arrow_middle = tip_x - arrow_half_width; 118 119 SkPath arrow; 120 arrow.moveTo(arrow_middle - arrow_half_width, kArrowHeight); 121 arrow.lineTo(arrow_middle + arrow_half_width, kArrowHeight); 122 arrow.lineTo(arrow_middle, 0); 123 arrow.close(); 124 canvas->ClipPath(arrow); 125 canvas->DrawColor(color); 126} 127 128// This class handles layout for the first row of a SuggestionView. 129// It exists to circumvent shortcomings of GridLayout and BoxLayout (namely that 130// the former doesn't fully respect child visibility, and that the latter won't 131// expand a single child). 132class SectionRowView : public views::View { 133 public: 134 SectionRowView() {} 135 virtual ~SectionRowView() {} 136 137 // views::View implementation: 138 virtual gfx::Size GetPreferredSize() OVERRIDE { 139 // Only the height matters anyway. 140 return child_at(2)->GetPreferredSize(); 141 } 142 143 virtual void Layout() OVERRIDE { 144 const gfx::Rect bounds = GetContentsBounds(); 145 146 // Icon is left aligned. 147 int start_x = bounds.x(); 148 views::View* icon = child_at(0); 149 if (icon->visible()) { 150 icon->SizeToPreferredSize(); 151 icon->SetX(start_x); 152 icon->SetY(bounds.y() + 153 (bounds.height() - icon->bounds().height()) / 2); 154 start_x += icon->bounds().width() + kAroundTextPadding; 155 } 156 157 // Textfield is right aligned. 158 int end_x = bounds.width(); 159 views::View* decorated = child_at(2); 160 if (decorated->visible()) { 161 decorated->SizeToPreferredSize(); 162 decorated->SetX(bounds.width() - decorated->bounds().width()); 163 decorated->SetY(bounds.y()); 164 end_x = decorated->bounds().x() - kAroundTextPadding; 165 } 166 167 // Label takes up all the space in between. 168 views::View* label = child_at(1); 169 if (label->visible()) 170 label->SetBounds(start_x, bounds.y(), end_x - start_x, bounds.height()); 171 172 views::View::Layout(); 173 } 174 175 private: 176 DISALLOW_COPY_AND_ASSIGN(SectionRowView); 177}; 178 179// This view is used for the contents of the error bubble widget. 180class ErrorBubbleContents : public views::View { 181 public: 182 explicit ErrorBubbleContents(const string16& message) 183 : color_(kWarningColor) { 184 set_border(views::Border::CreateEmptyBorder(kArrowHeight, 0, 0, 0)); 185 186 views::Label* label = new views::Label(); 187 label->SetText(message); 188 label->SetAutoColorReadabilityEnabled(false); 189 label->SetEnabledColor(SK_ColorWHITE); 190 label->set_border( 191 views::Border::CreateSolidSidedBorder(5, 10, 5, 10, color_)); 192 label->set_background( 193 views::Background::CreateSolidBackground(color_)); 194 SetLayoutManager(new views::FillLayout()); 195 AddChildView(label); 196 } 197 virtual ~ErrorBubbleContents() {} 198 199 virtual void OnPaint(gfx::Canvas* canvas) OVERRIDE { 200 views::View::OnPaint(canvas); 201 DrawArrow(canvas, width() / 2.0f, color_); 202 } 203 204 private: 205 SkColor color_; 206 207 DISALLOW_COPY_AND_ASSIGN(ErrorBubbleContents); 208}; 209 210// A view that runs a callback whenever its bounds change. 211class DetailsContainerView : public views::View { 212 public: 213 explicit DetailsContainerView(const base::Closure& callback) 214 : bounds_changed_callback_(callback) {} 215 virtual ~DetailsContainerView() {} 216 217 // views::View implementation. 218 virtual void OnBoundsChanged(const gfx::Rect& previous_bounds) OVERRIDE { 219 bounds_changed_callback_.Run(); 220 } 221 222 private: 223 base::Closure bounds_changed_callback_; 224 225 DISALLOW_COPY_AND_ASSIGN(DetailsContainerView); 226}; 227 228// ButtonStripView wraps the Autocheckout progress bar and the "[X] Save details 229// in Chrome" checkbox and listens for visibility changes. 230class ButtonStripView : public views::View { 231 public: 232 ButtonStripView() {} 233 virtual ~ButtonStripView() {} 234 235 protected: 236 virtual void ChildVisibilityChanged(views::View* child) OVERRIDE { 237 PreferredSizeChanged(); 238 } 239 240 private: 241 DISALLOW_COPY_AND_ASSIGN(ButtonStripView); 242}; 243 244} // namespace 245 246// AutofillDialogViews::SizeLimitedScrollView ---------------------------------- 247 248AutofillDialogViews::SizeLimitedScrollView::SizeLimitedScrollView( 249 views::View* scroll_contents) 250 : max_height_(-1) { 251 set_hide_horizontal_scrollbar(true); 252 SetContents(scroll_contents); 253} 254 255AutofillDialogViews::SizeLimitedScrollView::~SizeLimitedScrollView() {} 256 257void AutofillDialogViews::SizeLimitedScrollView::Layout() { 258 contents()->SizeToPreferredSize(); 259 ScrollView::Layout(); 260} 261 262gfx::Size AutofillDialogViews::SizeLimitedScrollView::GetPreferredSize() { 263 gfx::Size size = contents()->GetPreferredSize(); 264 if (max_height_ >= 0 && max_height_ < size.height()) 265 size.set_height(max_height_); 266 267 return size; 268} 269 270void AutofillDialogViews::SizeLimitedScrollView::SetMaximumHeight( 271 int max_height) { 272 int old_max = max_height_; 273 max_height_ = max_height; 274 275 if (max_height_ < height() || old_max <= height()) 276 PreferredSizeChanged(); 277} 278 279// AutofillDialogViews::ErrorBubble -------------------------------------------- 280 281AutofillDialogViews::ErrorBubble::ErrorBubble(views::View* anchor, 282 const string16& message) 283 : anchor_(anchor), 284 contents_(new ErrorBubbleContents(message)), 285 observer_(this) { 286 widget_ = new views::Widget; 287 views::Widget::InitParams params(views::Widget::InitParams::TYPE_POPUP); 288 params.transparent = true; 289 views::Widget* anchor_widget = anchor->GetWidget(); 290 DCHECK(anchor_widget); 291 params.parent = anchor_widget->GetNativeView(); 292 293 widget_->Init(params); 294 widget_->SetContentsView(contents_); 295 UpdatePosition(); 296 observer_.Add(widget_); 297} 298 299AutofillDialogViews::ErrorBubble::~ErrorBubble() { 300 if (widget_) 301 widget_->Close(); 302} 303 304bool AutofillDialogViews::ErrorBubble::IsShowing() { 305 return widget_ && widget_->IsVisible(); 306} 307 308void AutofillDialogViews::ErrorBubble::UpdatePosition() { 309 if (!widget_) 310 return; 311 312 if (!anchor_->GetVisibleBounds().IsEmpty()) { 313 widget_->SetBounds(GetBoundsForWidget()); 314 widget_->SetVisibilityChangedAnimationsEnabled(true); 315 widget_->Show(); 316 } else { 317 widget_->SetVisibilityChangedAnimationsEnabled(false); 318 widget_->Hide(); 319 } 320} 321 322void AutofillDialogViews::ErrorBubble::OnWidgetClosing(views::Widget* widget) { 323 DCHECK_EQ(widget_, widget); 324 observer_.Remove(widget_); 325 widget_ = NULL; 326} 327 328gfx::Rect AutofillDialogViews::ErrorBubble::GetBoundsForWidget() { 329 gfx::Rect anchor_bounds = anchor_->GetBoundsInScreen(); 330 gfx::Rect bubble_bounds; 331 bubble_bounds.set_size(contents_->GetPreferredSize()); 332 bubble_bounds.set_x(anchor_bounds.right() - 333 (anchor_bounds.width() + bubble_bounds.width()) / 2); 334 const int kErrorBubbleOverlap = 3; 335 bubble_bounds.set_y(anchor_bounds.bottom() - kErrorBubbleOverlap); 336 337 return bubble_bounds; 338} 339 340// AutofillDialogViews::DecoratedTextfield ------------------------------------- 341 342AutofillDialogViews::DecoratedTextfield::DecoratedTextfield( 343 const string16& default_value, 344 const string16& placeholder, 345 views::TextfieldController* controller) 346 : textfield_(new views::Textfield()), 347 invalid_(false) { 348 textfield_->set_placeholder_text(placeholder); 349 textfield_->SetText(default_value); 350 textfield_->SetController(controller); 351 SetLayoutManager(new views::FillLayout()); 352 AddChildView(textfield_); 353} 354 355AutofillDialogViews::DecoratedTextfield::~DecoratedTextfield() {} 356 357void AutofillDialogViews::DecoratedTextfield::SetInvalid(bool invalid) { 358 invalid_ = invalid; 359 if (invalid) 360 textfield_->SetBorderColor(kWarningColor); 361 else 362 textfield_->UseDefaultBorderColor(); 363 SchedulePaint(); 364} 365 366const char* AutofillDialogViews::DecoratedTextfield::GetClassName() const { 367 return kDecoratedTextfieldClassName; 368} 369 370void AutofillDialogViews::DecoratedTextfield::PaintChildren( 371 gfx::Canvas* canvas) {} 372 373void AutofillDialogViews::DecoratedTextfield::OnPaint(gfx::Canvas* canvas) { 374 // Draw the textfield first. 375 canvas->Save(); 376 if (FlipCanvasOnPaintForRTLUI()) { 377 canvas->Translate(gfx::Vector2d(width(), 0)); 378 canvas->Scale(-1, 1); 379 } 380 views::View::PaintChildren(canvas); 381 canvas->Restore(); 382 383 // Then draw extra stuff on top. 384 if (invalid_) { 385 SkPath dog_ear; 386 dog_ear.moveTo(width() - kDogEarSize, 0); 387 dog_ear.lineTo(width(), 0); 388 dog_ear.lineTo(width(), kDogEarSize); 389 dog_ear.close(); 390 canvas->ClipPath(dog_ear); 391 canvas->DrawColor(kWarningColor); 392 } 393} 394 395void AutofillDialogViews::DecoratedTextfield::RequestFocus() { 396 textfield()->RequestFocus(); 397} 398 399// AutofillDialogViews::AccountChooser ----------------------------------------- 400 401AutofillDialogViews::AccountChooser::AccountChooser( 402 AutofillDialogController* controller) 403 : image_(new views::ImageView()), 404 label_(new views::Label()), 405 arrow_(new views::ImageView()), 406 link_(new views::Link()), 407 controller_(controller) { 408 SetLayoutManager( 409 new views::BoxLayout(views::BoxLayout::kHorizontal, 0, 0, 410 kAroundTextPadding)); 411 AddChildView(image_); 412 AddChildView(label_); 413 414 arrow_->SetImage(ui::ResourceBundle::GetSharedInstance().GetImageNamed( 415 IDR_MENU_DROPARROW).ToImageSkia()); 416 AddChildView(arrow_); 417 418 link_->set_listener(this); 419 AddChildView(link_); 420} 421 422AutofillDialogViews::AccountChooser::~AccountChooser() {} 423 424void AutofillDialogViews::AccountChooser::Update() { 425 gfx::Image icon = controller_->AccountChooserImage(); 426 image_->SetImage(icon.AsImageSkia()); 427 label_->SetText(controller_->AccountChooserText()); 428 429 bool show_link = !controller_->MenuModelForAccountChooser(); 430 label_->SetVisible(!show_link); 431 arrow_->SetVisible(!show_link); 432 link_->SetText(controller_->SignInLinkText()); 433 link_->SetVisible(show_link); 434 435 menu_runner_.reset(); 436 437 PreferredSizeChanged(); 438} 439 440bool AutofillDialogViews::AccountChooser::OnMousePressed( 441 const ui::MouseEvent& event) { 442 // Return true so we get the release event. 443 if (controller_->MenuModelForAccountChooser()) 444 return event.IsOnlyLeftMouseButton(); 445 446 return false; 447} 448 449void AutofillDialogViews::AccountChooser::OnMouseReleased( 450 const ui::MouseEvent& event) { 451 if (!HitTestPoint(event.location())) 452 return; 453 454 ui::MenuModel* model = controller_->MenuModelForAccountChooser(); 455 if (!model) 456 return; 457 458 menu_runner_.reset(new views::MenuRunner(model)); 459 ignore_result( 460 menu_runner_->RunMenuAt(GetWidget(), 461 NULL, 462 GetBoundsInScreen(), 463 views::MenuItemView::TOPRIGHT, 464 0)); 465} 466 467void AutofillDialogViews::AccountChooser::LinkClicked(views::Link* source, 468 int event_flags) { 469 controller_->SignInLinkClicked(); 470} 471 472// AutofillDialogViews::NotificationArea --------------------------------------- 473 474AutofillDialogViews::NotificationArea::NotificationArea( 475 AutofillDialogController* controller) 476 : controller_(controller), 477 checkbox_(NULL) { 478 // Reserve vertical space for the arrow (regardless of whether one exists). 479 set_border(views::Border::CreateEmptyBorder(kArrowHeight, 0, 0, 0)); 480 481 views::BoxLayout* box_layout = 482 new views::BoxLayout(views::BoxLayout::kVertical, 0, 0, 0); 483 SetLayoutManager(box_layout); 484} 485 486AutofillDialogViews::NotificationArea::~NotificationArea() {} 487 488void AutofillDialogViews::NotificationArea::SetNotifications( 489 const std::vector<DialogNotification>& notifications) { 490 notifications_ = notifications; 491 492 RemoveAllChildViews(true); 493 checkbox_ = NULL; 494 495 if (notifications_.empty()) 496 return; 497 498 for (size_t i = 0; i < notifications_.size(); ++i) { 499 const DialogNotification& notification = notifications_[i]; 500 501 scoped_ptr<views::View> view; 502 if (notification.HasCheckbox()) { 503 scoped_ptr<views::Checkbox> checkbox(new views::Checkbox(string16())); 504 checkbox_ = checkbox.get(); 505 // We have to do this instead of using set_border() because a border 506 // is being used to draw the check square. 507 static_cast<views::LabelButtonBorder*>(checkbox->border())-> 508 set_insets(gfx::Insets(kNotificationPadding, 509 kNotificationPadding, 510 kNotificationPadding, 511 kNotificationPadding)); 512 if (!notification.interactive()) 513 checkbox->SetState(views::Button::STATE_DISABLED); 514 checkbox->SetText(notification.display_text()); 515 checkbox->SetTextMultiLine(true); 516 checkbox->SetHorizontalAlignment(gfx::ALIGN_LEFT); 517 checkbox->SetTextColor(views::Button::STATE_NORMAL, 518 notification.GetTextColor()); 519 checkbox->SetTextColor(views::Button::STATE_HOVERED, 520 notification.GetTextColor()); 521 checkbox->SetChecked(notification.checked()); 522 checkbox->set_listener(this); 523 view.reset(checkbox.release()); 524 } else { 525 scoped_ptr<views::Label> label(new views::Label()); 526 label->SetText(notification.display_text()); 527 label->SetMultiLine(true); 528 label->SetHorizontalAlignment(gfx::ALIGN_LEFT); 529 label->SetAutoColorReadabilityEnabled(false); 530 label->SetEnabledColor(notification.GetTextColor()); 531 label->set_border(views::Border::CreateSolidBorder( 532 kNotificationPadding, notification.GetBackgroundColor())); 533 view.reset(label.release()); 534 } 535 536 view->set_background(views::Background::CreateSolidBackground( 537 notification.GetBackgroundColor())); 538 AddChildView(view.release()); 539 } 540 541 PreferredSizeChanged(); 542} 543 544const char* AutofillDialogViews::NotificationArea::GetClassName() const { 545 return kNotificationAreaClassName; 546} 547 548void AutofillDialogViews::NotificationArea::OnPaint(gfx::Canvas* canvas) { 549 views::View::OnPaint(canvas); 550 551 if (HasArrow()) { 552 DrawArrow( 553 canvas, 554 GetMirroredXInView(width() - arrow_centering_anchor_->width() / 2.0f), 555 notifications_[0].GetBackgroundColor()); 556 } 557} 558 559void AutofillDialogViews::OnWidgetClosing(views::Widget* widget) { 560 observer_.Remove(widget); 561} 562 563void AutofillDialogViews::OnWidgetBoundsChanged(views::Widget* widget, 564 const gfx::Rect& new_bounds) { 565 int non_scrollable_height = window_->GetContentsView()->bounds().height() - 566 scrollable_area_->bounds().height(); 567 int browser_window_height = widget->GetContentsView()->bounds().height(); 568 569 scrollable_area_->SetMaximumHeight( 570 std::max(kMinimumContentsHeight, 571 (browser_window_height - non_scrollable_height) * 8 / 10)); 572 ContentsPreferredSizeChanged(); 573} 574 575void AutofillDialogViews::NotificationArea::ButtonPressed( 576 views::Button* sender, const ui::Event& event) { 577 DCHECK_EQ(sender, checkbox_); 578 controller_->NotificationCheckboxStateChanged(notifications_.front().type(), 579 checkbox_->checked()); 580} 581 582bool AutofillDialogViews::NotificationArea::HasArrow() { 583 return !notifications_.empty() && notifications_[0].HasArrow() && 584 arrow_centering_anchor_.get(); 585} 586 587// AutofillDialogViews::SectionContainer --------------------------------------- 588 589AutofillDialogViews::SectionContainer::SectionContainer( 590 const string16& label, 591 views::View* controls, 592 views::Button* proxy_button) 593 : proxy_button_(proxy_button), 594 forward_mouse_events_(false) { 595 set_notify_enter_exit_on_child(true); 596 597 views::GridLayout* layout = new views::GridLayout(this); 598 layout->SetInsets(kDetailSectionInset, 0, kDetailSectionInset, 0); 599 SetLayoutManager(layout); 600 601 const int kColumnSetId = 0; 602 views::ColumnSet* column_set = layout->AddColumnSet(kColumnSetId); 603 // TODO(estade): pull out these constants, and figure out better values 604 // for them. 605 column_set->AddColumn(views::GridLayout::FILL, 606 views::GridLayout::LEADING, 607 0, 608 views::GridLayout::FIXED, 609 180, 610 0); 611 column_set->AddPaddingColumn(0, 30); 612 column_set->AddColumn(views::GridLayout::FILL, 613 views::GridLayout::LEADING, 614 0, 615 views::GridLayout::FIXED, 616 300, 617 0); 618 619 layout->StartRow(0, kColumnSetId); 620 layout->AddView(CreateDetailsSectionLabel(label)); 621 layout->AddView(controls); 622} 623 624AutofillDialogViews::SectionContainer::~SectionContainer() {} 625 626void AutofillDialogViews::SectionContainer::SetActive(bool active) { 627 bool is_active = active && proxy_button_->visible(); 628 if (is_active == !!background()) 629 return; 630 631 set_background(is_active ? 632 views::Background::CreateSolidBackground(kShadingColor) : 633 NULL); 634 SchedulePaint(); 635} 636 637void AutofillDialogViews::SectionContainer::SetForwardMouseEvents( 638 bool forward) { 639 forward_mouse_events_ = forward; 640 if (!forward) 641 set_background(NULL); 642} 643 644void AutofillDialogViews::SectionContainer::OnMouseMoved( 645 const ui::MouseEvent& event) { 646 if (!forward_mouse_events_) 647 return; 648 649 SetActive(true); 650} 651 652void AutofillDialogViews::SectionContainer::OnMouseEntered( 653 const ui::MouseEvent& event) { 654 if (!forward_mouse_events_) 655 return; 656 657 SetActive(true); 658 proxy_button_->OnMouseEntered(ProxyEvent(event)); 659 SchedulePaint(); 660} 661 662void AutofillDialogViews::SectionContainer::OnMouseExited( 663 const ui::MouseEvent& event) { 664 if (!forward_mouse_events_) 665 return; 666 667 SetActive(false); 668 proxy_button_->OnMouseExited(ProxyEvent(event)); 669 SchedulePaint(); 670} 671 672bool AutofillDialogViews::SectionContainer::OnMousePressed( 673 const ui::MouseEvent& event) { 674 if (!forward_mouse_events_) 675 return false; 676 677 return proxy_button_->OnMousePressed(ProxyEvent(event)); 678} 679 680void AutofillDialogViews::SectionContainer::OnMouseReleased( 681 const ui::MouseEvent& event) { 682 if (!forward_mouse_events_) 683 return; 684 685 proxy_button_->OnMouseReleased(ProxyEvent(event)); 686} 687 688// static 689ui::MouseEvent AutofillDialogViews::SectionContainer::ProxyEvent( 690 const ui::MouseEvent& event) { 691 ui::MouseEvent event_copy = event; 692 event_copy.set_location(gfx::Point()); 693 return event_copy; 694} 695 696// AutofilDialogViews::SuggestionView ------------------------------------------ 697 698AutofillDialogViews::SuggestionView::SuggestionView( 699 const string16& edit_label, 700 AutofillDialogViews* autofill_dialog) 701 : label_(new views::Label()), 702 label_line_2_(new views::Label()), 703 icon_(new views::ImageView()), 704 label_container_(new SectionRowView()), 705 decorated_( 706 new DecoratedTextfield(string16(), string16(), autofill_dialog)), 707 edit_link_(new views::Link(edit_label)) { 708 // Label and icon. 709 label_->SetHorizontalAlignment(gfx::ALIGN_LEFT); 710 label_->set_border(CreateLabelAlignmentBorder()); 711 label_container_->AddChildView(icon_); 712 label_container_->AddChildView(label_); 713 label_container_->AddChildView(decorated_); 714 decorated_->SetVisible(false); 715 // TODO(estade): get the sizing and spacing right on this textfield. 716 decorated_->textfield()->set_default_width_in_chars(10); 717 AddChildView(label_container_); 718 719 label_line_2_->SetHorizontalAlignment(gfx::ALIGN_LEFT); 720 label_line_2_->SetVisible(false); 721 AddChildView(label_line_2_); 722 723 // TODO(estade): The link needs to have a different color when hovered. 724 edit_link_->set_listener(autofill_dialog); 725 edit_link_->SetHorizontalAlignment(gfx::ALIGN_LEFT); 726 edit_link_->SetUnderline(false); 727 728 // This container prevents the edit link from being horizontally stretched. 729 views::View* link_container = new views::View(); 730 link_container->SetLayoutManager( 731 new views::BoxLayout(views::BoxLayout::kHorizontal, 0, 0, 0)); 732 link_container->AddChildView(edit_link_); 733 AddChildView(link_container); 734 735 SetLayoutManager(new views::BoxLayout(views::BoxLayout::kVertical, 0, 0, 0)); 736} 737 738AutofillDialogViews::SuggestionView::~SuggestionView() {} 739 740void AutofillDialogViews::SuggestionView::SetEditable(bool editable) { 741 edit_link_->SetVisible(editable); 742} 743 744void AutofillDialogViews::SuggestionView::SetSuggestionText( 745 const string16& text, 746 gfx::Font::FontStyle text_style) { 747 label_->SetFont(ui::ResourceBundle::GetSharedInstance().GetFont( 748 ui::ResourceBundle::BaseFont).DeriveFont(0, text_style)); 749 750 // TODO(estade): does this localize well? 751 string16 line_return(ASCIIToUTF16("\n")); 752 size_t position = text.find(line_return); 753 if (position == string16::npos) { 754 label_->SetText(text); 755 label_line_2_->SetVisible(false); 756 } else { 757 label_->SetText(text.substr(0, position)); 758 label_line_2_->SetText(text.substr(position + line_return.length())); 759 label_line_2_->SetVisible(true); 760 } 761} 762 763void AutofillDialogViews::SuggestionView::SetSuggestionIcon( 764 const gfx::Image& image) { 765 icon_->SetVisible(!image.IsEmpty()); 766 icon_->SetImage(image.AsImageSkia()); 767} 768 769void AutofillDialogViews::SuggestionView::ShowTextfield( 770 const string16& placeholder_text, 771 const gfx::ImageSkia& icon) { 772 decorated_->textfield()->set_placeholder_text(placeholder_text); 773 decorated_->textfield()->SetIcon(icon); 774 decorated_->SetVisible(true); 775 // The textfield will increase the height of the first row and cause the 776 // label to be aligned properly, so the border is not necessary. 777 label_->set_border(NULL); 778} 779 780// AutofilDialogViews::AutocheckoutProgressBar --------------------------------- 781 782AutofillDialogViews::AutocheckoutProgressBar::AutocheckoutProgressBar() {} 783AutofillDialogViews::AutocheckoutProgressBar::~AutocheckoutProgressBar() {} 784 785gfx::Size AutofillDialogViews::AutocheckoutProgressBar::GetPreferredSize() { 786 return gfx::Size(kAutocheckoutProgressBarWidth, 787 kAutocheckoutProgressBarHeight); 788} 789 790// AutofillDialogView ---------------------------------------------------------- 791 792// static 793AutofillDialogView* AutofillDialogView::Create( 794 AutofillDialogController* controller) { 795 return new AutofillDialogViews(controller); 796} 797 798// AutofillDialogViews --------------------------------------------------------- 799 800AutofillDialogViews::AutofillDialogViews(AutofillDialogController* controller) 801 : controller_(controller), 802 window_(NULL), 803 contents_(NULL), 804 notification_area_(NULL), 805 account_chooser_(NULL), 806 sign_in_webview_(NULL), 807 main_container_(NULL), 808 scrollable_area_(NULL), 809 details_container_(NULL), 810 button_strip_extra_view_(NULL), 811 save_in_chrome_checkbox_(NULL), 812 autocheckout_progress_bar_view_(NULL), 813 autocheckout_progress_bar_(NULL), 814 footnote_view_(NULL), 815 legal_document_view_(NULL), 816 focus_manager_(NULL), 817 observer_(this) { 818 DCHECK(controller); 819 detail_groups_.insert(std::make_pair(SECTION_EMAIL, 820 DetailsGroup(SECTION_EMAIL))); 821 detail_groups_.insert(std::make_pair(SECTION_CC, 822 DetailsGroup(SECTION_CC))); 823 detail_groups_.insert(std::make_pair(SECTION_BILLING, 824 DetailsGroup(SECTION_BILLING))); 825 detail_groups_.insert(std::make_pair(SECTION_CC_BILLING, 826 DetailsGroup(SECTION_CC_BILLING))); 827 detail_groups_.insert(std::make_pair(SECTION_SHIPPING, 828 DetailsGroup(SECTION_SHIPPING))); 829} 830 831AutofillDialogViews::~AutofillDialogViews() { 832 DCHECK(!window_); 833} 834 835void AutofillDialogViews::Show() { 836 InitChildViews(); 837 UpdateAccountChooser(); 838 UpdateNotificationArea(); 839 UpdateSaveInChromeCheckbox(); 840 841 // Ownership of |contents_| is handed off by this call. The widget will take 842 // care of deleting itself after calling DeleteDelegate(). 843 WebContentsModalDialogManager* web_contents_modal_dialog_manager = 844 WebContentsModalDialogManager::FromWebContents( 845 controller_->web_contents()); 846 window_ = CreateWebContentsModalDialogViews( 847 this, 848 controller_->web_contents()->GetView()->GetNativeView(), 849 web_contents_modal_dialog_manager->delegate()-> 850 GetWebContentsModalDialogHost()); 851 web_contents_modal_dialog_manager->ShowDialog(window_->GetNativeView()); 852 focus_manager_ = window_->GetFocusManager(); 853 focus_manager_->AddFocusChangeListener(this); 854 855#if defined(OS_WIN) && !defined(USE_AURA) 856 // On non-Aura Windows a standard accelerator gets registered that will 857 // navigate on a backspace. Override that here. 858 // TODO(abodenha): Remove this when no longer needed. See 859 // http://crbug.com/242584. 860 ui::Accelerator backspace(ui::VKEY_BACK, ui::EF_NONE); 861 focus_manager_->RegisterAccelerator( 862 backspace, ui::AcceleratorManager::kNormalPriority, this); 863#endif 864 865 // Listen for size changes on the browser. 866 views::Widget* browser_widget = 867 views::Widget::GetTopLevelWidgetForNativeView( 868 controller_->web_contents()->GetView()->GetNativeView()); 869 observer_.Add(browser_widget); 870 OnWidgetBoundsChanged(browser_widget, gfx::Rect()); 871} 872 873void AutofillDialogViews::Hide() { 874 if (window_) 875 window_->Close(); 876} 877 878void AutofillDialogViews::UpdateAccountChooser() { 879 account_chooser_->Update(); 880 881 // Update legal documents for the account. 882 if (footnote_view_) { 883 const string16 text = controller_->LegalDocumentsText(); 884 legal_document_view_->SetText(text); 885 886 if (!text.empty()) { 887 const std::vector<ui::Range>& link_ranges = 888 controller_->LegalDocumentLinks(); 889 for (size_t i = 0; i < link_ranges.size(); ++i) { 890 legal_document_view_->AddStyleRange( 891 link_ranges[i], 892 views::StyledLabel::RangeStyleInfo::CreateForLink()); 893 } 894 } 895 896 footnote_view_->SetVisible(!text.empty()); 897 ContentsPreferredSizeChanged(); 898 } 899} 900 901void AutofillDialogViews::UpdateButtonStrip() { 902 button_strip_extra_view_->SetVisible( 903 GetDialogButtons() != ui::DIALOG_BUTTON_NONE); 904 UpdateSaveInChromeCheckbox(); 905 autocheckout_progress_bar_view_->SetVisible( 906 controller_->ShouldShowProgressBar()); 907 GetDialogClientView()->UpdateDialogButtons(); 908 ContentsPreferredSizeChanged(); 909} 910 911void AutofillDialogViews::UpdateDetailArea() { 912 details_container_->SetVisible(controller_->ShouldShowDetailArea()); 913 ContentsPreferredSizeChanged(); 914} 915 916void AutofillDialogViews::UpdateNotificationArea() { 917 DCHECK(notification_area_); 918 notification_area_->SetNotifications(controller_->CurrentNotifications()); 919 ContentsPreferredSizeChanged(); 920} 921 922void AutofillDialogViews::UpdateSection(DialogSection section) { 923 UpdateSectionImpl(section, true); 924} 925 926void AutofillDialogViews::FillSection(DialogSection section, 927 const DetailInput& originating_input) { 928 DetailsGroup* group = GroupForSection(section); 929 // Make sure to overwrite the originating input. 930 TextfieldMap::iterator text_mapping = 931 group->textfields.find(&originating_input); 932 if (text_mapping != group->textfields.end()) 933 text_mapping->second->textfield()->SetText(string16()); 934 935 // If the Autofill data comes from a credit card, make sure to overwrite the 936 // CC comboboxes (even if they already have something in them). If the 937 // Autofill data comes from an AutofillProfile, leave the comboboxes alone. 938 if ((section == SECTION_CC || section == SECTION_CC_BILLING) && 939 AutofillType(originating_input.type).group() == 940 AutofillType::CREDIT_CARD) { 941 for (ComboboxMap::const_iterator it = group->comboboxes.begin(); 942 it != group->comboboxes.end(); ++it) { 943 if (AutofillType(it->first->type).group() == AutofillType::CREDIT_CARD) 944 it->second->SetSelectedIndex(it->second->model()->GetDefaultIndex()); 945 } 946 } 947 948 UpdateSectionImpl(section, false); 949} 950 951void AutofillDialogViews::GetUserInput(DialogSection section, 952 DetailOutputMap* output) { 953 DetailsGroup* group = GroupForSection(section); 954 for (TextfieldMap::const_iterator it = group->textfields.begin(); 955 it != group->textfields.end(); ++it) { 956 output->insert(std::make_pair(it->first, it->second->textfield()->text())); 957 } 958 for (ComboboxMap::const_iterator it = group->comboboxes.begin(); 959 it != group->comboboxes.end(); ++it) { 960 output->insert(std::make_pair(it->first, 961 it->second->model()->GetItemAt(it->second->selected_index()))); 962 } 963} 964 965string16 AutofillDialogViews::GetCvc() { 966 DialogSection billing_section = controller_->SectionIsActive(SECTION_CC) ? 967 SECTION_CC : SECTION_CC_BILLING; 968 return GroupForSection(billing_section)->suggested_info-> 969 decorated_textfield()->textfield()->text(); 970} 971 972bool AutofillDialogViews::SaveDetailsLocally() { 973 DCHECK(save_in_chrome_checkbox_->visible()); 974 return save_in_chrome_checkbox_->checked(); 975} 976 977const content::NavigationController* AutofillDialogViews::ShowSignIn() { 978 // TODO(abodenha) We should be able to use the WebContents of the WebView 979 // to navigate instead of LoadInitialURL. Figure out why it doesn't work. 980 981 sign_in_webview_->LoadInitialURL(wallet::GetSignInUrl()); 982 983 main_container_->SetVisible(false); 984 sign_in_webview_->SetVisible(true); 985 UpdateButtonStrip(); 986 ContentsPreferredSizeChanged(); 987 return &sign_in_webview_->web_contents()->GetController(); 988} 989 990void AutofillDialogViews::HideSignIn() { 991 sign_in_webview_->SetVisible(false); 992 main_container_->SetVisible(true); 993 UpdateButtonStrip(); 994 ContentsPreferredSizeChanged(); 995} 996 997void AutofillDialogViews::UpdateProgressBar(double value) { 998 autocheckout_progress_bar_->SetValue(value); 999} 1000 1001void AutofillDialogViews::ModelChanged() { 1002 menu_runner_.reset(); 1003 1004 for (DetailGroupMap::const_iterator iter = detail_groups_.begin(); 1005 iter != detail_groups_.end(); ++iter) { 1006 UpdateDetailsGroupState(iter->second); 1007 } 1008} 1009 1010TestableAutofillDialogView* AutofillDialogViews::GetTestableView() { 1011 return this; 1012} 1013 1014void AutofillDialogViews::SubmitForTesting() { 1015 Accept(); 1016} 1017 1018void AutofillDialogViews::CancelForTesting() { 1019 if (Cancel()) 1020 Hide(); 1021} 1022 1023string16 AutofillDialogViews::GetTextContentsOfInput(const DetailInput& input) { 1024 views::Textfield* textfield = TextfieldForInput(input); 1025 if (textfield) 1026 return textfield->text(); 1027 1028 views::Combobox* combobox = ComboboxForInput(input); 1029 if (combobox) 1030 return combobox->model()->GetItemAt(combobox->selected_index()); 1031 1032 NOTREACHED(); 1033 return string16(); 1034} 1035 1036void AutofillDialogViews::SetTextContentsOfInput(const DetailInput& input, 1037 const string16& contents) { 1038 views::Textfield* textfield = TextfieldForInput(input); 1039 if (textfield) { 1040 TextfieldForInput(input)->SetText(contents); 1041 return; 1042 } 1043 1044 views::Combobox* combobox = ComboboxForInput(input); 1045 if (combobox) { 1046 for (int i = 0; i < combobox->model()->GetItemCount(); ++i) { 1047 if (contents == combobox->model()->GetItemAt(i)) { 1048 combobox->SetSelectedIndex(i); 1049 return; 1050 } 1051 } 1052 // If we don't find a match, return the combobox to its default state. 1053 combobox->SetSelectedIndex(combobox->model()->GetDefaultIndex()); 1054 return; 1055 } 1056 1057 NOTREACHED(); 1058} 1059 1060void AutofillDialogViews::ActivateInput(const DetailInput& input) { 1061 TextfieldEditedOrActivated(TextfieldForInput(input), false); 1062} 1063 1064void AutofillDialogViews::OnSignInResize(const gfx::Size& pref_size) { 1065 sign_in_webview_->SetPreferredSize(pref_size); 1066 ContentsPreferredSizeChanged(); 1067} 1068 1069bool AutofillDialogViews::AcceleratorPressed( 1070 const ui::Accelerator& accelerator) { 1071 ui::KeyboardCode key = accelerator.key_code(); 1072 if (key == ui::VKEY_BACK) 1073 return true; 1074 return false; 1075} 1076 1077bool AutofillDialogViews::CanHandleAccelerators() const { 1078 return true; 1079} 1080 1081string16 AutofillDialogViews::GetWindowTitle() const { 1082 return controller_->DialogTitle(); 1083} 1084 1085void AutofillDialogViews::WindowClosing() { 1086 focus_manager_->RemoveFocusChangeListener(this); 1087} 1088 1089void AutofillDialogViews::DeleteDelegate() { 1090 window_ = NULL; 1091 // |this| belongs to |controller_|. 1092 controller_->ViewClosed(); 1093} 1094 1095views::Widget* AutofillDialogViews::GetWidget() { 1096 return contents_->GetWidget(); 1097} 1098 1099const views::Widget* AutofillDialogViews::GetWidget() const { 1100 return contents_->GetWidget(); 1101} 1102 1103views::View* AutofillDialogViews::GetContentsView() { 1104 return contents_; 1105} 1106 1107int AutofillDialogViews::GetDialogButtons() const { 1108 if (sign_in_webview_->visible()) 1109 return ui::DIALOG_BUTTON_NONE; 1110 1111 return controller_->GetDialogButtons(); 1112} 1113 1114string16 AutofillDialogViews::GetDialogButtonLabel(ui::DialogButton button) 1115 const { 1116 return button == ui::DIALOG_BUTTON_OK ? 1117 controller_->ConfirmButtonText() : controller_->CancelButtonText(); 1118} 1119 1120bool AutofillDialogViews::IsDialogButtonEnabled(ui::DialogButton button) const { 1121 return controller_->IsDialogButtonEnabled(button); 1122} 1123 1124views::View* AutofillDialogViews::CreateExtraView() { 1125 return button_strip_extra_view_; 1126} 1127 1128views::View* AutofillDialogViews::CreateTitlebarExtraView() { 1129 return account_chooser_; 1130} 1131 1132views::View* AutofillDialogViews::CreateFootnoteView() { 1133 footnote_view_ = new views::View(); 1134 footnote_view_->SetLayoutManager( 1135 new views::BoxLayout(views::BoxLayout::kVertical, 1136 kLegalDocPadding, 1137 kLegalDocPadding, 1138 0)); 1139 footnote_view_->set_border( 1140 views::Border::CreateSolidSidedBorder(1, 0, 0, 0, kSubtleBorderColor)); 1141 footnote_view_->set_background( 1142 views::Background::CreateSolidBackground(kShadingColor)); 1143 1144 legal_document_view_ = new views::StyledLabel(string16(), this); 1145 footnote_view_->AddChildView(legal_document_view_); 1146 footnote_view_->SetVisible(false); 1147 1148 return footnote_view_; 1149} 1150 1151bool AutofillDialogViews::Cancel() { 1152 controller_->OnCancel(); 1153 return true; 1154} 1155 1156bool AutofillDialogViews::Accept() { 1157 if (ValidateForm()) 1158 controller_->OnAccept(); 1159 else if (!validity_map_.empty()) 1160 validity_map_.begin()->first->RequestFocus(); 1161 1162 // |controller_| decides when to hide the dialog. 1163 return false; 1164} 1165 1166// TODO(wittman): Remove this override once we move to the new style frame view 1167// on all dialogs. 1168views::NonClientFrameView* AutofillDialogViews::CreateNonClientFrameView( 1169 views::Widget* widget) { 1170 return CreateConstrainedStyleNonClientFrameView( 1171 widget, 1172 controller_->web_contents()->GetBrowserContext()); 1173} 1174 1175void AutofillDialogViews::ButtonPressed(views::Button* sender, 1176 const ui::Event& event) { 1177 // TODO(estade): Should the menu be shown on mouse down? 1178 DetailsGroup* group = NULL; 1179 for (DetailGroupMap::iterator iter = detail_groups_.begin(); 1180 iter != detail_groups_.end(); ++iter) { 1181 if (sender == iter->second.suggested_button) { 1182 group = &iter->second; 1183 break; 1184 } 1185 } 1186 DCHECK(group); 1187 1188 if (!group->suggested_button->visible()) 1189 return; 1190 1191 menu_runner_.reset(new views::MenuRunner( 1192 controller_->MenuModelForSection(group->section))); 1193 1194 group->container->SetActive(true); 1195 views::Button::ButtonState state = group->suggested_button->state(); 1196 group->suggested_button->SetState(views::Button::STATE_PRESSED); 1197 // Ignore the result since we don't need to handle a deleted menu specially. 1198 gfx::Rect bounds = group->suggested_button->GetBoundsInScreen(); 1199 bounds.Inset(group->suggested_button->GetInsets()); 1200 ignore_result( 1201 menu_runner_->RunMenuAt(sender->GetWidget(), 1202 NULL, 1203 bounds, 1204 views::MenuItemView::TOPRIGHT, 1205 0)); 1206 group->container->SetActive(false); 1207 group->suggested_button->SetState(state); 1208} 1209 1210void AutofillDialogViews::ContentsChanged(views::Textfield* sender, 1211 const string16& new_contents) { 1212 TextfieldEditedOrActivated(sender, true); 1213} 1214 1215bool AutofillDialogViews::HandleKeyEvent(views::Textfield* sender, 1216 const ui::KeyEvent& key_event) { 1217 scoped_ptr<ui::KeyEvent> copy(key_event.Copy()); 1218#if defined(OS_WIN) && !defined(USE_AURA) 1219 content::NativeWebKeyboardEvent event(copy->native_event()); 1220#else 1221 content::NativeWebKeyboardEvent event(copy.get()); 1222#endif 1223 return controller_->HandleKeyPressEventInInput(event); 1224} 1225 1226bool AutofillDialogViews::HandleMouseEvent(views::Textfield* sender, 1227 const ui::MouseEvent& mouse_event) { 1228 if (mouse_event.IsLeftMouseButton() && sender->HasFocus()) { 1229 TextfieldEditedOrActivated(sender, false); 1230 // Show an error bubble if a user clicks on an input that's already focused 1231 // (and invalid). 1232 ShowErrorBubbleForViewIfNecessary(sender); 1233 } 1234 1235 return false; 1236} 1237 1238void AutofillDialogViews::OnWillChangeFocus( 1239 views::View* focused_before, 1240 views::View* focused_now) { 1241 controller_->FocusMoved(); 1242 error_bubble_.reset(); 1243} 1244 1245void AutofillDialogViews::OnDidChangeFocus( 1246 views::View* focused_before, 1247 views::View* focused_now) { 1248 // If user leaves an edit-field, revalidate the group it belongs to. 1249 if (focused_before) { 1250 DetailsGroup* group = GroupForView(focused_before); 1251 if (group && group->container->visible()) 1252 ValidateGroup(*group, AutofillDialogController::VALIDATE_EDIT); 1253 } 1254 1255 // Show an error bubble when the user focuses the input. 1256 if (focused_now) 1257 ShowErrorBubbleForViewIfNecessary(focused_now); 1258} 1259 1260void AutofillDialogViews::LinkClicked(views::Link* source, int event_flags) { 1261 // Edit links. 1262 for (DetailGroupMap::iterator iter = detail_groups_.begin(); 1263 iter != detail_groups_.end(); ++iter) { 1264 if (iter->second.suggested_info->Contains(source)) { 1265 controller_->EditClickedForSection(iter->first); 1266 return; 1267 } 1268 } 1269} 1270 1271void AutofillDialogViews::OnSelectedIndexChanged(views::Combobox* combobox) { 1272 DetailsGroup* group = GroupForView(combobox); 1273 ValidateGroup(*group, AutofillDialogController::VALIDATE_EDIT); 1274} 1275 1276void AutofillDialogViews::StyledLabelLinkClicked(const ui::Range& range, 1277 int event_flags) { 1278 controller_->LegalDocumentLinkClicked(range); 1279} 1280 1281void AutofillDialogViews::InitChildViews() { 1282 button_strip_extra_view_ = new ButtonStripView(); 1283 button_strip_extra_view_->SetLayoutManager( 1284 new views::BoxLayout(views::BoxLayout::kHorizontal, 0, 0, 0)); 1285 1286 save_in_chrome_checkbox_ = 1287 new views::Checkbox(controller_->SaveLocallyText()); 1288 save_in_chrome_checkbox_->SetChecked(true); 1289 button_strip_extra_view_->AddChildView(save_in_chrome_checkbox_); 1290 1291 autocheckout_progress_bar_view_ = new views::View(); 1292 autocheckout_progress_bar_view_->SetLayoutManager( 1293 new views::BoxLayout(views::BoxLayout::kVertical, 0, 0, 0)); 1294 1295 views::Label* progress_bar_label = new views::Label(); 1296 progress_bar_label->SetText(controller_->ProgressBarText()); 1297 progress_bar_label->SetHorizontalAlignment(gfx::ALIGN_LEFT); 1298 autocheckout_progress_bar_view_->AddChildView(progress_bar_label); 1299 1300 autocheckout_progress_bar_ = new AutocheckoutProgressBar(); 1301 autocheckout_progress_bar_view_->AddChildView(autocheckout_progress_bar_); 1302 1303 button_strip_extra_view_->AddChildView(autocheckout_progress_bar_view_); 1304 autocheckout_progress_bar_view_->SetVisible(false); 1305 1306 contents_ = new views::View(); 1307 contents_->SetLayoutManager( 1308 new views::BoxLayout(views::BoxLayout::kHorizontal, 0, 0, 0)); 1309 contents_->AddChildView(CreateMainContainer()); 1310 sign_in_webview_ = new views::WebView(controller_->profile()); 1311 sign_in_webview_->SetVisible(false); 1312 contents_->AddChildView(sign_in_webview_); 1313 sign_in_delegate_.reset( 1314 new AutofillDialogSignInDelegate(this, 1315 sign_in_webview_->GetWebContents())); 1316} 1317 1318views::View* AutofillDialogViews::CreateMainContainer() { 1319 main_container_ = new views::View(); 1320 main_container_->SetLayoutManager( 1321 new views::BoxLayout(views::BoxLayout::kVertical, 0, 0, 1322 views::kRelatedControlVerticalSpacing)); 1323 1324 account_chooser_ = new AccountChooser(controller_); 1325 if (!views::DialogDelegate::UseNewStyle()) 1326 main_container_->AddChildView(account_chooser_); 1327 1328 notification_area_ = new NotificationArea(controller_); 1329 notification_area_->set_arrow_centering_anchor(account_chooser_->AsWeakPtr()); 1330 main_container_->AddChildView(notification_area_); 1331 1332 scrollable_area_ = new SizeLimitedScrollView(CreateDetailsContainer()); 1333 main_container_->AddChildView(scrollable_area_); 1334 return main_container_; 1335} 1336 1337views::View* AutofillDialogViews::CreateDetailsContainer() { 1338 details_container_ = new DetailsContainerView( 1339 base::Bind(&AutofillDialogViews::DetailsContainerBoundsChanged, 1340 base::Unretained(this))); 1341 // A box layout is used because it respects widget visibility. 1342 details_container_->SetLayoutManager( 1343 new views::BoxLayout(views::BoxLayout::kVertical, 0, 0, 0)); 1344 for (DetailGroupMap::iterator iter = detail_groups_.begin(); 1345 iter != detail_groups_.end(); ++iter) { 1346 CreateDetailsSection(iter->second.section); 1347 details_container_->AddChildView(iter->second.container); 1348 } 1349 1350 return details_container_; 1351} 1352 1353void AutofillDialogViews::CreateDetailsSection(DialogSection section) { 1354 // Inputs container (manual inputs + combobox). 1355 views::View* inputs_container = CreateInputsContainer(section); 1356 1357 DetailsGroup* group = GroupForSection(section); 1358 // Container (holds label + inputs). 1359 group->container = new SectionContainer( 1360 controller_->LabelForSection(section), 1361 inputs_container, 1362 group->suggested_button); 1363 UpdateDetailsGroupState(*group); 1364} 1365 1366views::View* AutofillDialogViews::CreateInputsContainer(DialogSection section) { 1367 views::View* inputs_container = new views::View(); 1368 views::GridLayout* layout = new views::GridLayout(inputs_container); 1369 inputs_container->SetLayoutManager(layout); 1370 1371 int kColumnSetId = 0; 1372 views::ColumnSet* column_set = layout->AddColumnSet(kColumnSetId); 1373 column_set->AddColumn(views::GridLayout::FILL, 1374 views::GridLayout::LEADING, 1375 1, 1376 views::GridLayout::USE_PREF, 1377 0, 1378 0); 1379 // A column for the menu button. 1380 column_set->AddColumn(views::GridLayout::CENTER, 1381 views::GridLayout::LEADING, 1382 0, 1383 views::GridLayout::USE_PREF, 1384 0, 1385 0); 1386 layout->StartRow(0, kColumnSetId); 1387 1388 // The |info_view| holds |manual_inputs| and |suggested_info|, allowing the 1389 // dialog to toggle which is shown. 1390 views::View* info_view = new views::View(); 1391 info_view->SetLayoutManager( 1392 new views::BoxLayout(views::BoxLayout::kVertical, 0, 0, 0)); 1393 1394 views::View* manual_inputs = InitInputsView(section); 1395 info_view->AddChildView(manual_inputs); 1396 SuggestionView* suggested_info = 1397 new SuggestionView(controller_->EditSuggestionText(), this); 1398 info_view->AddChildView(suggested_info); 1399 layout->AddView(info_view); 1400 1401 views::ImageButton* menu_button = new views::ImageButton(this); 1402 ui::ResourceBundle& rb = ui::ResourceBundle::GetSharedInstance(); 1403 menu_button->SetImage(views::Button::STATE_NORMAL, 1404 rb.GetImageSkiaNamed(IDR_AUTOFILL_DIALOG_MENU_BUTTON)); 1405 menu_button->SetImage(views::Button::STATE_PRESSED, 1406 rb.GetImageSkiaNamed(IDR_AUTOFILL_DIALOG_MENU_BUTTON_P)); 1407 menu_button->SetImage(views::Button::STATE_HOVERED, 1408 rb.GetImageSkiaNamed(IDR_AUTOFILL_DIALOG_MENU_BUTTON_H)); 1409 menu_button->SetImage(views::Button::STATE_DISABLED, 1410 rb.GetImageSkiaNamed(IDR_AUTOFILL_DIALOG_MENU_BUTTON_D)); 1411 menu_button->set_border(views::Border::CreateEmptyBorder( 1412 kMenuButtonTopOffset, 1413 kMenuButtonHorizontalPadding, 1414 0, 1415 kMenuButtonHorizontalPadding)); 1416 layout->AddView(menu_button); 1417 1418 DetailsGroup* group = GroupForSection(section); 1419 group->suggested_button = menu_button; 1420 group->manual_input = manual_inputs; 1421 group->suggested_info = suggested_info; 1422 return inputs_container; 1423} 1424 1425// TODO(estade): we should be using Chrome-style constrained window padding 1426// values. 1427views::View* AutofillDialogViews::InitInputsView(DialogSection section) { 1428 const DetailInputs& inputs = controller_->RequestedFieldsForSection(section); 1429 TextfieldMap* textfields = &GroupForSection(section)->textfields; 1430 ComboboxMap* comboboxes = &GroupForSection(section)->comboboxes; 1431 1432 views::View* view = new views::View(); 1433 views::GridLayout* layout = new views::GridLayout(view); 1434 view->SetLayoutManager(layout); 1435 1436 for (DetailInputs::const_iterator it = inputs.begin(); 1437 it != inputs.end(); ++it) { 1438 const DetailInput& input = *it; 1439 int kColumnSetId = input.row_id; 1440 views::ColumnSet* column_set = layout->GetColumnSet(kColumnSetId); 1441 if (!column_set) { 1442 // Create a new column set and row. 1443 column_set = layout->AddColumnSet(kColumnSetId); 1444 if (it != inputs.begin()) 1445 layout->AddPaddingRow(0, views::kRelatedControlVerticalSpacing); 1446 layout->StartRow(0, kColumnSetId); 1447 } else { 1448 // Add a new column to existing row. 1449 column_set->AddPaddingColumn(0, views::kRelatedControlHorizontalSpacing); 1450 // Must explicitly skip the padding column since we've already started 1451 // adding views. 1452 layout->SkipColumns(1); 1453 } 1454 1455 float expand = input.expand_weight; 1456 column_set->AddColumn(views::GridLayout::FILL, 1457 views::GridLayout::BASELINE, 1458 expand ? expand : 1, 1459 views::GridLayout::USE_PREF, 1460 0, 1461 0); 1462 1463 ui::ComboboxModel* input_model = 1464 controller_->ComboboxModelForAutofillType(input.type); 1465 if (input_model) { 1466 views::Combobox* combobox = new views::Combobox(input_model); 1467 combobox->set_listener(this); 1468 comboboxes->insert(std::make_pair(&input, combobox)); 1469 layout->AddView(combobox); 1470 1471 for (int i = 0; i < input_model->GetItemCount(); ++i) { 1472 if (input.initial_value == input_model->GetItemAt(i)) { 1473 combobox->SetSelectedIndex(i); 1474 break; 1475 } 1476 } 1477 } else { 1478 DecoratedTextfield* field = new DecoratedTextfield( 1479 input.initial_value, 1480 l10n_util::GetStringUTF16(input.placeholder_text_rid), 1481 this); 1482 1483 gfx::Image icon = 1484 controller_->IconForField(input.type, input.initial_value); 1485 field->textfield()->SetIcon(icon.AsImageSkia()); 1486 1487 textfields->insert(std::make_pair(&input, field)); 1488 layout->AddView(field); 1489 } 1490 } 1491 1492 return view; 1493} 1494 1495void AutofillDialogViews::UpdateSectionImpl( 1496 DialogSection section, 1497 bool clobber_inputs) { 1498 const DetailInputs& updated_inputs = 1499 controller_->RequestedFieldsForSection(section); 1500 DetailsGroup* group = GroupForSection(section); 1501 1502 for (DetailInputs::const_iterator iter = updated_inputs.begin(); 1503 iter != updated_inputs.end(); ++iter) { 1504 const DetailInput& input = *iter; 1505 TextfieldMap::iterator text_mapping = group->textfields.find(&input); 1506 1507 if (text_mapping != group->textfields.end()) { 1508 views::Textfield* textfield = text_mapping->second->textfield(); 1509 textfield->SetEnabled(input.editable); 1510 if (textfield->text().empty() || clobber_inputs) { 1511 textfield->SetText(iter->initial_value); 1512 textfield->SetIcon(controller_->IconForField( 1513 input.type, textfield->text()).AsImageSkia()); 1514 } 1515 } 1516 1517 ComboboxMap::iterator combo_mapping = group->comboboxes.find(&input); 1518 if (combo_mapping != group->comboboxes.end()) { 1519 views::Combobox* combobox = combo_mapping->second; 1520 combobox->SetEnabled(input.editable); 1521 if (combobox->selected_index() == combobox->model()->GetDefaultIndex() || 1522 clobber_inputs) { 1523 for (int i = 0; i < combobox->model()->GetItemCount(); ++i) { 1524 if (input.initial_value == combobox->model()->GetItemAt(i)) { 1525 combobox->SetSelectedIndex(i); 1526 break; 1527 } 1528 } 1529 } 1530 } 1531 } 1532 1533 UpdateDetailsGroupState(*group); 1534} 1535 1536void AutofillDialogViews::UpdateDetailsGroupState(const DetailsGroup& group) { 1537 const SuggestionState& suggestion_state = 1538 controller_->SuggestionStateForSection(group.section); 1539 bool show_suggestions = !suggestion_state.text.empty(); 1540 group.suggested_info->SetVisible(show_suggestions); 1541 group.suggested_info->SetSuggestionText(suggestion_state.text, 1542 suggestion_state.text_style); 1543 group.suggested_info->SetSuggestionIcon(suggestion_state.icon); 1544 group.suggested_info->SetEditable(suggestion_state.editable); 1545 1546 if (!suggestion_state.extra_text.empty()) { 1547 group.suggested_info->ShowTextfield( 1548 suggestion_state.extra_text, 1549 suggestion_state.extra_icon.AsImageSkia()); 1550 } 1551 1552 group.manual_input->SetVisible(!show_suggestions); 1553 1554 // Show or hide the "Save in chrome" checkbox. If nothing is in editing mode, 1555 // hide. If the controller tells us not to show it, likewise hide. 1556 UpdateSaveInChromeCheckbox(); 1557 1558 const bool has_menu = !!controller_->MenuModelForSection(group.section); 1559 1560 if (group.suggested_button) 1561 group.suggested_button->SetVisible(has_menu); 1562 1563 if (group.container) { 1564 group.container->SetForwardMouseEvents(has_menu && show_suggestions); 1565 group.container->SetVisible(controller_->SectionIsActive(group.section)); 1566 if (group.container->visible()) 1567 ValidateGroup(group, AutofillDialogController::VALIDATE_EDIT); 1568 } 1569 1570 ContentsPreferredSizeChanged(); 1571} 1572 1573template<class T> 1574void AutofillDialogViews::SetValidityForInput( 1575 T* input, 1576 const string16& message) { 1577 bool invalid = !message.empty(); 1578 input->SetInvalid(invalid); 1579 1580 if (invalid) { 1581 validity_map_[input] = message; 1582 } else { 1583 validity_map_.erase(input); 1584 1585 if (error_bubble_ && error_bubble_->anchor() == input) { 1586 validity_map_.erase(input); 1587 error_bubble_.reset(); 1588 } 1589 } 1590} 1591 1592void AutofillDialogViews::ShowErrorBubbleForViewIfNecessary(views::View* view) { 1593 if (!view->GetWidget()) 1594 return; 1595 1596 views::View* input = 1597 view->GetAncestorWithClassName(kDecoratedTextfieldClassName); 1598 if (!input) 1599 input = view; 1600 1601 if (error_bubble_ && error_bubble_->anchor() == input) 1602 return; 1603 1604 std::map<views::View*, string16>::iterator error_message = 1605 validity_map_.find(input); 1606 if (error_message != validity_map_.end()) 1607 error_bubble_.reset(new ErrorBubble(input, error_message->second)); 1608} 1609 1610bool AutofillDialogViews::ValidateGroup( 1611 const DetailsGroup& group, 1612 AutofillDialogController::ValidationType validation_type) { 1613 DCHECK(group.container->visible()); 1614 1615 scoped_ptr<DetailInput> cvc_input; 1616 DetailOutputMap detail_outputs; 1617 typedef std::map<AutofillFieldType, base::Callback<void(const string16&)> > 1618 FieldMap; 1619 FieldMap field_map; 1620 1621 if (group.manual_input->visible()) { 1622 for (TextfieldMap::const_iterator iter = group.textfields.begin(); 1623 iter != group.textfields.end(); ++iter) { 1624 if (!iter->first->editable) 1625 continue; 1626 1627 detail_outputs[iter->first] = iter->second->textfield()->text(); 1628 field_map[iter->first->type] = base::Bind( 1629 &AutofillDialogViews::SetValidityForInput<DecoratedTextfield>, 1630 base::Unretained(this), 1631 iter->second); 1632 } 1633 for (ComboboxMap::const_iterator iter = group.comboboxes.begin(); 1634 iter != group.comboboxes.end(); ++iter) { 1635 if (!iter->first->editable) 1636 continue; 1637 1638 views::Combobox* combobox = iter->second; 1639 string16 item = 1640 combobox->model()->GetItemAt(combobox->selected_index()); 1641 detail_outputs[iter->first] = item; 1642 field_map[iter->first->type] = base::Bind( 1643 &AutofillDialogViews::SetValidityForInput<views::Combobox>, 1644 base::Unretained(this), 1645 iter->second); 1646 } 1647 } else if (group.section == SECTION_CC) { 1648 DecoratedTextfield* decorated_cvc = 1649 group.suggested_info->decorated_textfield(); 1650 cvc_input.reset(new DetailInput); 1651 cvc_input->type = CREDIT_CARD_VERIFICATION_CODE; 1652 detail_outputs[cvc_input.get()] = decorated_cvc->textfield()->text(); 1653 field_map[cvc_input->type] = base::Bind( 1654 &AutofillDialogViews::SetValidityForInput<DecoratedTextfield>, 1655 base::Unretained(this), 1656 decorated_cvc); 1657 } 1658 1659 ValidityData invalid_inputs; 1660 invalid_inputs = controller_->InputsAreValid(detail_outputs, validation_type); 1661 // Flag invalid fields, removing them from |field_map|. 1662 for (ValidityData::const_iterator iter = 1663 invalid_inputs.begin(); iter != invalid_inputs.end(); ++iter) { 1664 const string16& message = iter->second; 1665 field_map[iter->first].Run(message); 1666 field_map.erase(iter->first); 1667 } 1668 1669 // The remaining fields in |field_map| are valid. Mark them as such. 1670 for (FieldMap::iterator iter = field_map.begin(); iter != field_map.end(); 1671 ++iter) { 1672 iter->second.Run(string16()); 1673 } 1674 1675 return invalid_inputs.empty(); 1676} 1677 1678bool AutofillDialogViews::ValidateForm() { 1679 bool all_valid = true; 1680 for (DetailGroupMap::iterator iter = detail_groups_.begin(); 1681 iter != detail_groups_.end(); ++iter) { 1682 const DetailsGroup& group = iter->second; 1683 if (!group.container->visible()) 1684 continue; 1685 1686 if (!ValidateGroup(group, AutofillDialogController::VALIDATE_FINAL)) 1687 all_valid = false; 1688 } 1689 1690 return all_valid; 1691} 1692 1693void AutofillDialogViews::TextfieldEditedOrActivated( 1694 views::Textfield* textfield, 1695 bool was_edit) { 1696 DetailsGroup* group = GroupForView(textfield); 1697 DCHECK(group); 1698 1699 // Figure out the AutofillFieldType this textfield represents. 1700 AutofillFieldType type = UNKNOWN_TYPE; 1701 DecoratedTextfield* decorated = NULL; 1702 1703 // Look for the input in the manual inputs. 1704 views::View* ancestor = 1705 textfield->GetAncestorWithClassName(kDecoratedTextfieldClassName); 1706 for (TextfieldMap::const_iterator iter = group->textfields.begin(); 1707 iter != group->textfields.end(); 1708 ++iter) { 1709 decorated = iter->second; 1710 if (decorated == ancestor) { 1711 controller_->UserEditedOrActivatedInput(iter->first, 1712 GetWidget()->GetNativeView(), 1713 textfield->GetBoundsInScreen(), 1714 textfield->text(), 1715 was_edit); 1716 type = iter->first->type; 1717 break; 1718 } 1719 } 1720 1721 if (ancestor == group->suggested_info->decorated_textfield()) { 1722 decorated = group->suggested_info->decorated_textfield(); 1723 type = CREDIT_CARD_VERIFICATION_CODE; 1724 } 1725 DCHECK_NE(UNKNOWN_TYPE, type); 1726 1727 // If the field is marked as invalid, check if the text is now valid. 1728 // Many fields (i.e. CC#) are invalid for most of the duration of editing, 1729 // so flagging them as invalid prematurely is not helpful. However, 1730 // correcting a minor mistake (i.e. a wrong CC digit) should immediately 1731 // result in validation - positive user feedback. 1732 if (decorated->invalid() && was_edit) { 1733 SetValidityForInput<DecoratedTextfield>( 1734 decorated, 1735 controller_->InputValidityMessage(type, textfield->text())); 1736 1737 // If the field transitioned from invalid to valid, re-validate the group, 1738 // since inter-field checks become meaningful with valid fields. 1739 if (!decorated->invalid()) 1740 ValidateGroup(*group, AutofillDialogController::VALIDATE_EDIT); 1741 } 1742 1743 gfx::Image icon = controller_->IconForField(type, textfield->text()); 1744 textfield->SetIcon(icon.AsImageSkia()); 1745} 1746 1747void AutofillDialogViews::UpdateSaveInChromeCheckbox() { 1748 save_in_chrome_checkbox_->SetVisible( 1749 controller_->ShouldOfferToSaveInChrome()); 1750} 1751 1752void AutofillDialogViews::ContentsPreferredSizeChanged() { 1753 if (GetWidget()) { 1754 GetWidget()->SetSize(GetWidget()->non_client_view()->GetPreferredSize()); 1755 // If the above line does not cause the dialog's size to change, |contents_| 1756 // may not be laid out. This will trigger a layout only if it's needed. 1757 contents_->SetBoundsRect(contents_->bounds()); 1758 1759 if (error_bubble_) 1760 error_bubble_->UpdatePosition(); 1761 } 1762} 1763 1764AutofillDialogViews::DetailsGroup* AutofillDialogViews::GroupForSection( 1765 DialogSection section) { 1766 return &detail_groups_.find(section)->second; 1767} 1768 1769AutofillDialogViews::DetailsGroup* AutofillDialogViews::GroupForView( 1770 views::View* view) { 1771 DCHECK(view); 1772 // Textfields are treated slightly differently. For them, inspect 1773 // the DecoratedTextfield ancestor, not the actual control. 1774 views::View* ancestor = 1775 view->GetAncestorWithClassName(kDecoratedTextfieldClassName); 1776 1777 views::View* control = ancestor ? ancestor : view; 1778 for (DetailGroupMap::iterator iter = detail_groups_.begin(); 1779 iter != detail_groups_.end(); ++iter) { 1780 DetailsGroup* group = &iter->second; 1781 if (control->parent() == group->manual_input) 1782 return group; 1783 1784 // Textfields need to check a second case, since they can be 1785 // suggested inputs instead of directly editable inputs. Those are 1786 // accessed via |suggested_info|. 1787 if (ancestor && 1788 ancestor == group->suggested_info->decorated_textfield()) { 1789 return group; 1790 } 1791 } 1792 return NULL; 1793} 1794 1795views::Textfield* AutofillDialogViews::TextfieldForInput( 1796 const DetailInput& input) { 1797 for (DetailGroupMap::iterator iter = detail_groups_.begin(); 1798 iter != detail_groups_.end(); ++iter) { 1799 const DetailsGroup& group = iter->second; 1800 TextfieldMap::const_iterator text_mapping = group.textfields.find(&input); 1801 if (text_mapping != group.textfields.end()) 1802 return text_mapping->second->textfield(); 1803 } 1804 1805 return NULL; 1806} 1807 1808views::Combobox* AutofillDialogViews::ComboboxForInput( 1809 const DetailInput& input) { 1810 for (DetailGroupMap::iterator iter = detail_groups_.begin(); 1811 iter != detail_groups_.end(); ++iter) { 1812 const DetailsGroup& group = iter->second; 1813 ComboboxMap::const_iterator combo_mapping = group.comboboxes.find(&input); 1814 if (combo_mapping != group.comboboxes.end()) 1815 return combo_mapping->second; 1816 } 1817 1818 return NULL; 1819} 1820 1821void AutofillDialogViews::DetailsContainerBoundsChanged() { 1822 if (error_bubble_) 1823 error_bubble_->UpdatePosition(); 1824} 1825 1826AutofillDialogViews::DetailsGroup::DetailsGroup(DialogSection section) 1827 : section(section), 1828 container(NULL), 1829 manual_input(NULL), 1830 suggested_info(NULL), 1831 suggested_button(NULL) {} 1832 1833AutofillDialogViews::DetailsGroup::~DetailsGroup() {} 1834 1835} // namespace autofill 1836