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