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