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