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