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