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