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