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