autofill_popup_controller_impl.cc revision 2a99a7e74a7f215066514fe81d2bfa6639d9eddd
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/autofill/autofill_popup_controller_impl.h" 6 7#include <algorithm> 8#include <utility> 9 10#include "base/logging.h" 11#include "base/utf_string_conversions.h" 12#include "chrome/browser/ui/autofill/autofill_popup_view.h" 13#include "components/autofill/browser/autofill_popup_delegate.h" 14#include "content/public/browser/native_web_keyboard_event.h" 15#include "grit/webkit_resources.h" 16#include "third_party/WebKit/Source/WebKit/chromium/public/WebAutofillClient.h" 17#include "ui/base/events/event.h" 18#include "ui/base/text/text_elider.h" 19#include "ui/gfx/display.h" 20#include "ui/gfx/rect_conversions.h" 21#include "ui/gfx/screen.h" 22#include "ui/gfx/vector2d.h" 23 24using base::WeakPtr; 25using WebKit::WebAutofillClient; 26 27namespace { 28 29// Used to indicate that no line is currently selected by the user. 30const int kNoSelection = -1; 31 32// Size difference between name and subtext in pixels. 33const int kLabelFontSizeDelta = -2; 34 35// The vertical height of each row in pixels. 36const size_t kRowHeight = 24; 37 38// The vertical height of a separator in pixels. 39const size_t kSeparatorHeight = 1; 40 41// The amount of minimum padding between the Autofill name and subtext in 42// pixels. 43const size_t kNamePadding = 15; 44 45// The maximum amount of characters to display from either the name or subtext. 46const size_t kMaxTextLength = 15; 47 48#if !defined(OS_ANDROID) 49const size_t kIconPadding = AutofillPopupView::kIconPadding; 50const size_t kEndPadding = AutofillPopupView::kEndPadding; 51const size_t kAutofillIconWidth = AutofillPopupView::kAutofillIconWidth; 52#endif 53 54struct DataResource { 55 const char* name; 56 int id; 57}; 58 59const DataResource kDataResources[] = { 60 { "americanExpressCC", IDR_AUTOFILL_CC_AMEX }, 61 { "dinersCC", IDR_AUTOFILL_CC_DINERS }, 62 { "discoverCC", IDR_AUTOFILL_CC_DISCOVER }, 63 { "genericCC", IDR_AUTOFILL_CC_GENERIC }, 64 { "jcbCC", IDR_AUTOFILL_CC_JCB }, 65 { "masterCardCC", IDR_AUTOFILL_CC_MASTERCARD }, 66 { "soloCC", IDR_AUTOFILL_CC_SOLO }, 67 { "visaCC", IDR_AUTOFILL_CC_VISA }, 68}; 69 70} // end namespace 71 72// static 73WeakPtr<AutofillPopupControllerImpl> AutofillPopupControllerImpl::GetOrCreate( 74 WeakPtr<AutofillPopupControllerImpl> previous, 75 AutofillPopupDelegate* delegate, 76 gfx::NativeView container_view, 77 const gfx::RectF& element_bounds) { 78 DCHECK(!previous || previous->delegate_ == delegate); 79 80 if (previous && 81 previous->container_view() == container_view && 82 previous->element_bounds() == element_bounds) { 83 return previous; 84 } 85 86 if (previous) 87 previous->Hide(); 88 89 AutofillPopupControllerImpl* controller = 90 new AutofillPopupControllerImpl(delegate, container_view, element_bounds); 91 return controller->GetWeakPtr(); 92} 93 94AutofillPopupControllerImpl::AutofillPopupControllerImpl( 95 AutofillPopupDelegate* delegate, 96 gfx::NativeView container_view, 97 const gfx::RectF& element_bounds) 98 : view_(NULL), 99 delegate_(delegate), 100 container_view_(container_view), 101 element_bounds_(element_bounds), 102 selected_line_(kNoSelection), 103 weak_ptr_factory_(this) { 104#if !defined(OS_ANDROID) 105 subtext_font_ = name_font_.DeriveFont(kLabelFontSizeDelta); 106 warning_font_ = name_font_.DeriveFont(0, gfx::Font::ITALIC); 107#endif 108} 109 110AutofillPopupControllerImpl::~AutofillPopupControllerImpl() {} 111 112void AutofillPopupControllerImpl::Show( 113 const std::vector<string16>& names, 114 const std::vector<string16>& subtexts, 115 const std::vector<string16>& icons, 116 const std::vector<int>& identifiers) { 117 names_ = names; 118 full_names_ = names; 119 subtexts_ = subtexts; 120 icons_ = icons; 121 identifiers_ = identifiers; 122 123#if !defined(OS_ANDROID) 124 // Android displays the long text with ellipsis using the view attributes. 125 126 UpdatePopupBounds(); 127 int popup_width = popup_bounds().width(); 128 129 // Elide the name and subtext strings so that the popup fits in the available 130 // space. 131 for (size_t i = 0; i < names_.size(); ++i) { 132 int name_width = GetNameFontForRow(i).GetStringWidth(names_[i]); 133 int subtext_width = subtext_font().GetStringWidth(subtexts_[i]); 134 int total_text_length = name_width + subtext_width; 135 136 // The line can have no strings if it represents a UI element, such as 137 // a separator line. 138 if (total_text_length == 0) 139 continue; 140 141 int available_width = popup_width - RowWidthWithoutText(i); 142 143 // Each field recieves space in proportion to its length. 144 int name_size = available_width * name_width / total_text_length; 145 names_[i] = ui::ElideText(names_[i], 146 GetNameFontForRow(i), 147 name_size, 148 ui::ELIDE_AT_END); 149 150 int subtext_size = available_width * subtext_width / total_text_length; 151 subtexts_[i] = ui::ElideText(subtexts_[i], 152 subtext_font(), 153 subtext_size, 154 ui::ELIDE_AT_END); 155 } 156#endif 157 158 if (!view_) { 159 view_ = AutofillPopupView::Create(this); 160 ShowView(); 161 } else { 162 UpdateBoundsAndRedrawPopup(); 163 } 164 165 delegate_->OnPopupShown(this); 166} 167 168void AutofillPopupControllerImpl::Hide() { 169 SetSelectedLine(kNoSelection); 170 171 delegate_->OnPopupHidden(this); 172 173 if (view_) 174 view_->Hide(); 175 176 delete this; 177} 178 179void AutofillPopupControllerImpl::ViewDestroyed() { 180 // The view has already been destroyed so clear the reference to it. 181 view_ = NULL; 182 183 Hide(); 184} 185 186bool AutofillPopupControllerImpl::HandleKeyPressEvent( 187 const content::NativeWebKeyboardEvent& event) { 188 switch (event.windowsKeyCode) { 189 case ui::VKEY_UP: 190 SelectPreviousLine(); 191 return true; 192 case ui::VKEY_DOWN: 193 SelectNextLine(); 194 return true; 195 case ui::VKEY_PRIOR: // Page up. 196 SetSelectedLine(0); 197 return true; 198 case ui::VKEY_NEXT: // Page down. 199 SetSelectedLine(names().size() - 1); 200 return true; 201 case ui::VKEY_ESCAPE: 202 Hide(); 203 return true; 204 case ui::VKEY_DELETE: 205 return (event.modifiers & content::NativeWebKeyboardEvent::ShiftKey) && 206 RemoveSelectedLine(); 207 case ui::VKEY_RETURN: 208 return AcceptSelectedLine(); 209 default: 210 return false; 211 } 212} 213 214void AutofillPopupControllerImpl::UpdateBoundsAndRedrawPopup() { 215#if !defined(OS_ANDROID) 216 // TODO(csharp): Since UpdatePopupBounds can change the position of the popup, 217 // the popup could end up jumping from above the element to below it. 218 // It is unclear if it is better to keep the popup where it was, or if it 219 // should try and move to its desired position. 220 UpdatePopupBounds(); 221#endif 222 223 view_->UpdateBoundsAndRedrawPopup(); 224} 225 226void AutofillPopupControllerImpl::MouseHovered(int x, int y) { 227 SetSelectedLine(LineFromY(y)); 228} 229 230void AutofillPopupControllerImpl::MouseClicked(int x, int y) { 231 MouseHovered(x, y); 232 AcceptSelectedLine(); 233} 234 235void AutofillPopupControllerImpl::MouseExitedPopup() { 236 SetSelectedLine(kNoSelection); 237} 238 239void AutofillPopupControllerImpl::AcceptSuggestion(size_t index) { 240 delegate_->DidAcceptSuggestion(full_names_[index], identifiers_[index]); 241} 242 243int AutofillPopupControllerImpl::GetIconResourceID( 244 const string16& resource_name) { 245 for (size_t i = 0; i < arraysize(kDataResources); ++i) { 246 if (resource_name == ASCIIToUTF16(kDataResources[i].name)) 247 return kDataResources[i].id; 248 } 249 250 return -1; 251} 252 253bool AutofillPopupControllerImpl::CanDelete(size_t index) const { 254 // TODO(isherman): Native AddressBook suggestions on Mac and Android should 255 // not be considered to be deleteable. 256 int id = identifiers_[index]; 257 return id > 0 || 258 id == WebAutofillClient::MenuItemIDAutocompleteEntry || 259 id == WebAutofillClient::MenuItemIDPasswordEntry; 260} 261 262gfx::Rect AutofillPopupControllerImpl::GetRowBounds(size_t index) { 263 int top = 0; 264 for (size_t i = 0; i < index; ++i) { 265 top += GetRowHeightFromId(identifiers()[i]); 266 } 267 268 return gfx::Rect( 269 0, 270 top, 271 popup_bounds_.width(), 272 GetRowHeightFromId(identifiers()[index])); 273} 274 275void AutofillPopupControllerImpl::SetPopupBounds(const gfx::Rect& bounds) { 276 popup_bounds_ = bounds; 277 UpdateBoundsAndRedrawPopup(); 278} 279 280const gfx::Rect& AutofillPopupControllerImpl::popup_bounds() const { 281 return popup_bounds_; 282} 283 284gfx::NativeView AutofillPopupControllerImpl::container_view() const { 285 return container_view_; 286} 287 288const gfx::RectF& AutofillPopupControllerImpl::element_bounds() const { 289 return element_bounds_; 290} 291 292const std::vector<string16>& AutofillPopupControllerImpl::names() const { 293 return names_; 294} 295 296const std::vector<string16>& AutofillPopupControllerImpl::subtexts() const { 297 return subtexts_; 298} 299 300const std::vector<string16>& AutofillPopupControllerImpl::icons() const { 301 return icons_; 302} 303 304const std::vector<int>& AutofillPopupControllerImpl::identifiers() const { 305 return identifiers_; 306} 307 308#if !defined(OS_ANDROID) 309const gfx::Font& AutofillPopupControllerImpl::GetNameFontForRow(size_t index) 310 const { 311 if (identifiers_[index] == WebAutofillClient::MenuItemIDWarningMessage) 312 return warning_font_; 313 314 return name_font_; 315} 316 317const gfx::Font& AutofillPopupControllerImpl::subtext_font() const { 318 return subtext_font_; 319} 320#endif 321 322int AutofillPopupControllerImpl::selected_line() const { 323 return selected_line_; 324} 325 326void AutofillPopupControllerImpl::SetSelectedLine(int selected_line) { 327 if (selected_line_ == selected_line) 328 return; 329 330 if (selected_line_ != kNoSelection) 331 InvalidateRow(selected_line_); 332 333 if (selected_line != kNoSelection) 334 InvalidateRow(selected_line); 335 336 selected_line_ = selected_line; 337 338 if (selected_line_ != kNoSelection) 339 delegate_->DidSelectSuggestion(identifiers_[selected_line_]); 340 else 341 delegate_->ClearPreviewedForm(); 342} 343 344void AutofillPopupControllerImpl::SelectNextLine() { 345 int new_selected_line = selected_line_ + 1; 346 347 // Skip over any lines that can't be selected. 348 while (static_cast<size_t>(new_selected_line) < names_.size() && 349 !CanAccept(identifiers()[new_selected_line])) { 350 ++new_selected_line; 351 } 352 353 if (new_selected_line == static_cast<int>(names_.size())) 354 new_selected_line = 0; 355 356 SetSelectedLine(new_selected_line); 357} 358 359void AutofillPopupControllerImpl::SelectPreviousLine() { 360 int new_selected_line = selected_line_ - 1; 361 362 // Skip over any lines that can't be selected. 363 while (new_selected_line > kNoSelection && 364 !CanAccept(identifiers()[new_selected_line])) { 365 --new_selected_line; 366 } 367 368 if (new_selected_line <= kNoSelection) 369 new_selected_line = names_.size() - 1; 370 371 SetSelectedLine(new_selected_line); 372} 373 374bool AutofillPopupControllerImpl::AcceptSelectedLine() { 375 if (selected_line_ == kNoSelection) 376 return false; 377 378 DCHECK_GE(selected_line_, 0); 379 DCHECK_LT(selected_line_, static_cast<int>(names_.size())); 380 381 if (!CanAccept(identifiers_[selected_line_])) 382 return false; 383 384 AcceptSuggestion(selected_line_); 385 return true; 386} 387 388bool AutofillPopupControllerImpl::RemoveSelectedLine() { 389 if (selected_line_ == kNoSelection) 390 return false; 391 392 DCHECK_GE(selected_line_, 0); 393 DCHECK_LT(selected_line_, static_cast<int>(names_.size())); 394 395 if (!CanDelete(selected_line_)) 396 return false; 397 398 delegate_->RemoveSuggestion(full_names_[selected_line_], 399 identifiers_[selected_line_]); 400 401 // Remove the deleted element. 402 names_.erase(names_.begin() + selected_line_); 403 full_names_.erase(full_names_.begin() + selected_line_); 404 subtexts_.erase(subtexts_.begin() + selected_line_); 405 icons_.erase(icons_.begin() + selected_line_); 406 identifiers_.erase(identifiers_.begin() + selected_line_); 407 408 SetSelectedLine(kNoSelection); 409 410 if (HasSuggestions()) { 411 delegate_->ClearPreviewedForm(); 412 UpdateBoundsAndRedrawPopup(); 413 } else { 414 Hide(); 415 } 416 417 return true; 418} 419 420int AutofillPopupControllerImpl::LineFromY(int y) { 421 int current_height = 0; 422 423 for (size_t i = 0; i < identifiers().size(); ++i) { 424 current_height += GetRowHeightFromId(identifiers()[i]); 425 426 if (y <= current_height) 427 return i; 428 } 429 430 // The y value goes beyond the popup so stop the selection at the last line. 431 return identifiers().size() - 1; 432} 433 434int AutofillPopupControllerImpl::GetRowHeightFromId(int identifier) const { 435 if (identifier == WebAutofillClient::MenuItemIDSeparator) 436 return kSeparatorHeight; 437 438 return kRowHeight; 439} 440 441bool AutofillPopupControllerImpl::CanAccept(int id) { 442 return id != WebAutofillClient::MenuItemIDSeparator && 443 id != WebAutofillClient::MenuItemIDWarningMessage; 444} 445 446bool AutofillPopupControllerImpl::HasSuggestions() { 447 return identifiers_.size() != 0 && 448 (identifiers_[0] > 0 || 449 identifiers_[0] == 450 WebAutofillClient::MenuItemIDAutocompleteEntry || 451 identifiers_[0] == WebAutofillClient::MenuItemIDPasswordEntry || 452 identifiers_[0] == WebAutofillClient::MenuItemIDDataListEntry); 453} 454 455void AutofillPopupControllerImpl::ShowView() { 456 view_->Show(); 457} 458 459void AutofillPopupControllerImpl::InvalidateRow(size_t row) { 460 view_->InvalidateRow(row); 461} 462 463#if !defined(OS_ANDROID) 464int AutofillPopupControllerImpl::GetDesiredPopupWidth() const { 465 if (!name_font_.platform_font() || !subtext_font_.platform_font()) { 466 // We can't calculate the size of the popup if the fonts 467 // aren't present. 468 return 0; 469 } 470 471 int popup_width = RoundedElementBounds().width(); 472 DCHECK_EQ(names().size(), subtexts().size()); 473 for (size_t i = 0; i < names().size(); ++i) { 474 int row_size = name_font_.GetStringWidth(names()[i]) + 475 subtext_font_.GetStringWidth(subtexts()[i]) + 476 RowWidthWithoutText(i); 477 478 popup_width = std::max(popup_width, row_size); 479 } 480 481 return popup_width; 482} 483 484int AutofillPopupControllerImpl::GetDesiredPopupHeight() const { 485 int popup_height = 0; 486 487 for (size_t i = 0; i < identifiers().size(); ++i) { 488 popup_height += GetRowHeightFromId(identifiers()[i]); 489 } 490 491 return popup_height; 492} 493 494int AutofillPopupControllerImpl::RowWidthWithoutText(int row) const { 495 int row_size = kEndPadding + kNamePadding; 496 497 // Add the Autofill icon size, if required. 498 if (!icons_[row].empty()) 499 row_size += kAutofillIconWidth + kIconPadding; 500 501 // Add the padding at the end 502 row_size += kEndPadding; 503 504 return row_size; 505} 506 507void AutofillPopupControllerImpl::UpdatePopupBounds() { 508 int popup_required_width = GetDesiredPopupWidth(); 509 int popup_height = GetDesiredPopupHeight(); 510 // This is the top left point of the popup if the popup is above the element 511 // and grows to the left (since that is the highest and furthest left the 512 // popup go could). 513 gfx::Point top_left_corner_of_popup = RoundedElementBounds().origin() + 514 gfx::Vector2d(RoundedElementBounds().width() - popup_required_width, 515 -popup_height); 516 517 // This is the bottom right point of the popup if the popup is below the 518 // element and grows to the right (since the is the lowest and furthest right 519 // the popup could go). 520 gfx::Point bottom_right_corner_of_popup = RoundedElementBounds().origin() + 521 gfx::Vector2d(popup_required_width, 522 RoundedElementBounds().height() + popup_height); 523 524 gfx::Display top_left_display = GetDisplayNearestPoint( 525 top_left_corner_of_popup); 526 gfx::Display bottom_right_display = GetDisplayNearestPoint( 527 bottom_right_corner_of_popup); 528 529 std::pair<int, int> popup_x_and_width = CalculatePopupXAndWidth( 530 top_left_display, bottom_right_display, popup_required_width); 531 std::pair<int, int> popup_y_and_height = CalculatePopupYAndHeight( 532 top_left_display, bottom_right_display, popup_height); 533 534 popup_bounds_ = gfx::Rect(popup_x_and_width.first, 535 popup_y_and_height.first, 536 popup_x_and_width.second, 537 popup_y_and_height.second); 538} 539#endif // !defined(OS_ANDROID) 540 541WeakPtr<AutofillPopupControllerImpl> AutofillPopupControllerImpl::GetWeakPtr() { 542 return weak_ptr_factory_.GetWeakPtr(); 543} 544 545const gfx::Rect AutofillPopupControllerImpl::RoundedElementBounds() const { 546 return gfx::ToEnclosingRect(element_bounds_); 547} 548 549gfx::Display AutofillPopupControllerImpl::GetDisplayNearestPoint( 550 const gfx::Point& point) const { 551 return gfx::Screen::GetScreenFor(container_view())->GetDisplayNearestPoint( 552 point); 553} 554 555std::pair<int, int> AutofillPopupControllerImpl::CalculatePopupXAndWidth( 556 const gfx::Display& left_display, 557 const gfx::Display& right_display, 558 int popup_required_width) const { 559 int leftmost_display_x = left_display.bounds().x() * 560 left_display.device_scale_factor(); 561 int rightmost_display_x = right_display.GetSizeInPixel().width() + 562 right_display.bounds().x() * right_display.device_scale_factor(); 563 564 // Calculate the start coordinates for the popup if it is growing right or 565 // the end position if it is growing to the left, capped to screen space. 566 int right_growth_start = std::max(leftmost_display_x, 567 std::min(rightmost_display_x, 568 RoundedElementBounds().x())); 569 int left_growth_end = std::max(leftmost_display_x, 570 std::min(rightmost_display_x, 571 RoundedElementBounds().right())); 572 573 int right_available = rightmost_display_x - right_growth_start; 574 int left_available = left_growth_end - leftmost_display_x; 575 576 int popup_width = std::min(popup_required_width, 577 std::max(right_available, left_available)); 578 579 // If there is enough space for the popup on the right, show it there, 580 // otherwise choose the larger size. 581 if (right_available >= popup_width || right_available >= left_available) 582 return std::make_pair(right_growth_start, popup_width); 583 else 584 return std::make_pair(left_growth_end - popup_width, popup_width); 585} 586 587std::pair<int,int> AutofillPopupControllerImpl::CalculatePopupYAndHeight( 588 const gfx::Display& top_display, 589 const gfx::Display& bottom_display, 590 int popup_required_height) const { 591 int topmost_display_y = top_display.bounds().y() * 592 top_display.device_scale_factor(); 593 int bottommost_display_y = bottom_display.GetSizeInPixel().height() + 594 (bottom_display.bounds().y() * 595 bottom_display.device_scale_factor()); 596 597 // Calculate the start coordinates for the popup if it is growing down or 598 // the end position if it is growing up, capped to screen space. 599 int top_growth_end = std::max(topmost_display_y, 600 std::min(bottommost_display_y, 601 RoundedElementBounds().y())); 602 int bottom_growth_start = std::max(topmost_display_y, 603 std::min(bottommost_display_y, RoundedElementBounds().bottom())); 604 605 int top_available = bottom_growth_start - topmost_display_y; 606 int bottom_available = bottommost_display_y - top_growth_end; 607 608 // TODO(csharp): Restrict the popup height to what is available. 609 if (bottom_available >= popup_required_height || 610 bottom_available >= top_available) { 611 // The popup can appear below the field. 612 return std::make_pair(bottom_growth_start, popup_required_height); 613 } else { 614 // The popup must appear above the field. 615 return std::make_pair(top_growth_end - popup_required_height, 616 popup_required_height); 617 } 618} 619