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