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