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