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