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.
5 #include "chrome/browser/ui/autofill/autofill_popup_controller_impl.h"
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 "content/public/browser/native_web_keyboard_event.h"
16 #include "grit/webkit_resources.h"
17 #include "third_party/WebKit/public/web/WebAutofillClient.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"
27 using blink::WebAutofillClient;
32 // Used to indicate that no line is currently selected by the user.
33 const int kNoSelection = -1;
35 // The vertical height of each row in pixels.
36 const size_t kRowHeight = 24;
38 // The vertical height of a separator in pixels.
39 const size_t kSeparatorHeight = 1;
41 #if !defined(OS_ANDROID)
42 // Size difference between name and subtext in pixels.
43 const int kLabelFontSizeDelta = -2;
45 const size_t kNamePadding = AutofillPopupView::kNamePadding;
46 const size_t kIconPadding = AutofillPopupView::kIconPadding;
47 const size_t kEndPadding = AutofillPopupView::kEndPadding;
55 const DataResource kDataResources[] = {
56 { "americanExpressCC", IDR_AUTOFILL_CC_AMEX },
57 { "dinersCC", IDR_AUTOFILL_CC_DINERS },
58 { "discoverCC", IDR_AUTOFILL_CC_DISCOVER },
59 { "genericCC", IDR_AUTOFILL_CC_GENERIC },
60 { "jcbCC", IDR_AUTOFILL_CC_JCB },
61 { "masterCardCC", IDR_AUTOFILL_CC_MASTERCARD },
62 { "visaCC", IDR_AUTOFILL_CC_VISA },
68 WeakPtr<AutofillPopupControllerImpl> AutofillPopupControllerImpl::GetOrCreate(
69 WeakPtr<AutofillPopupControllerImpl> previous,
70 WeakPtr<AutofillPopupDelegate> delegate,
71 content::WebContents* web_contents,
72 gfx::NativeView container_view,
73 const gfx::RectF& element_bounds,
74 base::i18n::TextDirection text_direction) {
75 DCHECK(!previous.get() || previous->delegate_.get() == delegate.get());
77 if (previous.get() && previous->web_contents() == web_contents &&
78 previous->container_view() == container_view &&
79 previous->element_bounds() == element_bounds) {
80 previous->ClearState();
87 AutofillPopupControllerImpl* controller =
88 new AutofillPopupControllerImpl(
89 delegate, web_contents, container_view, element_bounds,
91 return controller->GetWeakPtr();
94 AutofillPopupControllerImpl::AutofillPopupControllerImpl(
95 base::WeakPtr<AutofillPopupDelegate> delegate,
96 content::WebContents* web_contents,
97 gfx::NativeView container_view,
98 const gfx::RectF& element_bounds,
99 base::i18n::TextDirection text_direction)
100 : controller_common_(new PopupControllerCommon(element_bounds,
105 text_direction_(text_direction),
106 hide_on_outside_click_(false),
107 weak_ptr_factory_(this) {
109 controller_common_->SetKeyPressCallback(
110 base::Bind(&AutofillPopupControllerImpl::HandleKeyPressEvent,
111 base::Unretained(this)));
112 #if !defined(OS_ANDROID)
113 subtext_font_list_ = name_font_list_.DeriveFontListWithSizeDelta(
114 kLabelFontSizeDelta);
115 #if defined(OS_MACOSX)
116 // There is no italic version of the system font.
117 warning_font_list_ = name_font_list_;
119 warning_font_list_ = name_font_list_.DeriveFontListWithSizeDeltaAndStyle(
120 0, gfx::Font::ITALIC);
125 AutofillPopupControllerImpl::~AutofillPopupControllerImpl() {}
127 void AutofillPopupControllerImpl::Show(
128 const std::vector<base::string16>& names,
129 const std::vector<base::string16>& subtexts,
130 const std::vector<base::string16>& icons,
131 const std::vector<int>& identifiers) {
132 SetValues(names, subtexts, icons, identifiers);
134 #if !defined(OS_ANDROID)
135 // Android displays the long text with ellipsis using the view attributes.
138 int popup_width = popup_bounds().width();
140 // Elide the name and subtext strings so that the popup fits in the available
142 for (size_t i = 0; i < names_.size(); ++i) {
143 int name_width = gfx::GetStringWidth(names_[i], GetNameFontListForRow(i));
144 int subtext_width = gfx::GetStringWidth(subtexts_[i], subtext_font_list());
145 int total_text_length = name_width + subtext_width;
147 // The line can have no strings if it represents a UI element, such as
149 if (total_text_length == 0)
152 int available_width = popup_width - RowWidthWithoutText(i);
154 // Each field recieves space in proportion to its length.
155 int name_size = available_width * name_width / total_text_length;
156 names_[i] = gfx::ElideText(names_[i],
157 GetNameFontListForRow(i),
161 int subtext_size = available_width * subtext_width / total_text_length;
162 subtexts_[i] = gfx::ElideText(subtexts_[i],
170 view_ = AutofillPopupView::Create(this);
172 // It is possible to fail to create the popup, in this case
173 // treat the popup as hiding right away.
181 UpdateBoundsAndRedrawPopup();
184 delegate_->OnPopupShown();
185 controller_common_->RegisterKeyPressCallback();
188 void AutofillPopupControllerImpl::UpdateDataListValues(
189 const std::vector<base::string16>& values,
190 const std::vector<base::string16>& labels) {
191 // Remove all the old data list values, which should always be at the top of
192 // the list if they are present.
193 while (!identifiers_.empty() &&
194 identifiers_[0] == WebAutofillClient::MenuItemIDDataListEntry) {
195 names_.erase(names_.begin());
196 subtexts_.erase(subtexts_.begin());
197 icons_.erase(icons_.begin());
198 identifiers_.erase(identifiers_.begin());
201 // If there are no new data list values, exit (clearing the separator if there
203 if (values.empty()) {
204 if (!identifiers_.empty() &&
205 identifiers_[0] == WebAutofillClient::MenuItemIDSeparator) {
206 names_.erase(names_.begin());
207 subtexts_.erase(subtexts_.begin());
208 icons_.erase(icons_.begin());
209 identifiers_.erase(identifiers_.begin());
212 // The popup contents have changed, so either update the bounds or hide it.
213 if (HasSuggestions())
214 UpdateBoundsAndRedrawPopup();
221 // Add a separator if there are any other values.
222 if (!identifiers_.empty() &&
223 identifiers_[0] != WebAutofillClient::MenuItemIDSeparator) {
224 names_.insert(names_.begin(), base::string16());
225 subtexts_.insert(subtexts_.begin(), base::string16());
226 icons_.insert(icons_.begin(), base::string16());
227 identifiers_.insert(identifiers_.begin(),
228 WebAutofillClient::MenuItemIDSeparator);
232 names_.insert(names_.begin(), values.begin(), values.end());
233 subtexts_.insert(subtexts_.begin(), labels.begin(), labels.end());
235 // Add the values that are the same for all data list elements.
236 icons_.insert(icons_.begin(), values.size(), base::string16());
237 identifiers_.insert(identifiers_.begin(),
239 WebAutofillClient::MenuItemIDDataListEntry);
241 UpdateBoundsAndRedrawPopup();
244 void AutofillPopupControllerImpl::Hide() {
245 controller_common_->RemoveKeyPressCallback();
247 delegate_->OnPopupHidden();
255 void AutofillPopupControllerImpl::ViewDestroyed() {
256 // The view has already been destroyed so clear the reference to it.
262 bool AutofillPopupControllerImpl::HandleKeyPressEvent(
263 const content::NativeWebKeyboardEvent& event) {
264 switch (event.windowsKeyCode) {
266 SelectPreviousLine();
271 case ui::VKEY_PRIOR: // Page up.
274 case ui::VKEY_NEXT: // Page down.
275 SetSelectedLine(names().size() - 1);
277 case ui::VKEY_ESCAPE:
280 case ui::VKEY_DELETE:
281 return (event.modifiers & content::NativeWebKeyboardEvent::ShiftKey) &&
282 RemoveSelectedLine();
284 // A tab press should cause the selected line to be accepted, but still
285 // return false so the tab key press propagates and changes the cursor
287 AcceptSelectedLine();
289 case ui::VKEY_RETURN:
290 return AcceptSelectedLine();
296 void AutofillPopupControllerImpl::UpdateBoundsAndRedrawPopup() {
297 #if !defined(OS_ANDROID)
298 // TODO(csharp): Since UpdatePopupBounds can change the position of the popup,
299 // the popup could end up jumping from above the element to below it.
300 // It is unclear if it is better to keep the popup where it was, or if it
301 // should try and move to its desired position.
305 view_->UpdateBoundsAndRedrawPopup();
308 void AutofillPopupControllerImpl::SetSelectionAtPoint(const gfx::Point& point) {
309 SetSelectedLine(LineFromY(point.y()));
312 void AutofillPopupControllerImpl::AcceptSelectionAtPoint(
313 const gfx::Point& point) {
314 SetSelectionAtPoint(point);
315 AcceptSelectedLine();
318 void AutofillPopupControllerImpl::SelectionCleared() {
319 SetSelectedLine(kNoSelection);
322 bool AutofillPopupControllerImpl::ShouldRepostEvent(
323 const ui::MouseEvent& event) {
324 return delegate_->ShouldRepostEvent(event);
327 bool AutofillPopupControllerImpl::ShouldHideOnOutsideClick() const {
328 return hide_on_outside_click_;
331 void AutofillPopupControllerImpl::AcceptSuggestion(size_t index) {
332 delegate_->DidAcceptSuggestion(full_names_[index], identifiers_[index]);
335 int AutofillPopupControllerImpl::GetIconResourceID(
336 const base::string16& resource_name) const {
337 for (size_t i = 0; i < arraysize(kDataResources); ++i) {
338 if (resource_name == base::ASCIIToUTF16(kDataResources[i].name))
339 return kDataResources[i].id;
345 bool AutofillPopupControllerImpl::CanDelete(size_t index) const {
346 // TODO(isherman): Native AddressBook suggestions on Mac and Android should
347 // not be considered to be deleteable.
348 int id = identifiers_[index];
350 id == WebAutofillClient::MenuItemIDAutocompleteEntry ||
351 id == WebAutofillClient::MenuItemIDPasswordEntry;
354 bool AutofillPopupControllerImpl::IsWarning(size_t index) const {
355 return identifiers_[index] == WebAutofillClient::MenuItemIDWarningMessage;
358 gfx::Rect AutofillPopupControllerImpl::GetRowBounds(size_t index) {
359 int top = kPopupBorderThickness;
360 for (size_t i = 0; i < index; ++i) {
361 top += GetRowHeightFromId(identifiers()[i]);
365 kPopupBorderThickness,
367 popup_bounds_.width() - 2 * kPopupBorderThickness,
368 GetRowHeightFromId(identifiers()[index]));
371 void AutofillPopupControllerImpl::SetPopupBounds(const gfx::Rect& bounds) {
372 popup_bounds_ = bounds;
373 UpdateBoundsAndRedrawPopup();
376 const gfx::Rect& AutofillPopupControllerImpl::popup_bounds() const {
377 return popup_bounds_;
380 content::WebContents* AutofillPopupControllerImpl::web_contents() {
381 return controller_common_->web_contents();
384 gfx::NativeView AutofillPopupControllerImpl::container_view() {
385 return controller_common_->container_view();
388 const gfx::RectF& AutofillPopupControllerImpl::element_bounds() const {
389 return controller_common_->element_bounds();
392 bool AutofillPopupControllerImpl::IsRTL() const {
393 return text_direction_ == base::i18n::RIGHT_TO_LEFT;
396 const std::vector<base::string16>& AutofillPopupControllerImpl::names() const {
400 const std::vector<base::string16>& AutofillPopupControllerImpl::subtexts()
405 const std::vector<base::string16>& AutofillPopupControllerImpl::icons() const {
409 const std::vector<int>& AutofillPopupControllerImpl::identifiers() const {
413 #if !defined(OS_ANDROID)
414 const gfx::FontList& AutofillPopupControllerImpl::GetNameFontListForRow(
415 size_t index) const {
416 if (identifiers_[index] == WebAutofillClient::MenuItemIDWarningMessage)
417 return warning_font_list_;
419 return name_font_list_;
422 const gfx::FontList& AutofillPopupControllerImpl::subtext_font_list() const {
423 return subtext_font_list_;
427 int AutofillPopupControllerImpl::selected_line() const {
428 return selected_line_;
431 void AutofillPopupControllerImpl::set_hide_on_outside_click(
432 bool hide_on_outside_click) {
433 hide_on_outside_click_ = hide_on_outside_click;
436 void AutofillPopupControllerImpl::SetSelectedLine(int selected_line) {
437 if (selected_line_ == selected_line)
440 if (selected_line_ != kNoSelection &&
441 static_cast<size_t>(selected_line_) < identifiers_.size())
442 InvalidateRow(selected_line_);
444 if (selected_line != kNoSelection)
445 InvalidateRow(selected_line);
447 selected_line_ = selected_line;
449 if (selected_line_ != kNoSelection)
450 delegate_->DidSelectSuggestion(identifiers_[selected_line_]);
452 delegate_->ClearPreviewedForm();
455 void AutofillPopupControllerImpl::SelectNextLine() {
456 int new_selected_line = selected_line_ + 1;
458 // Skip over any lines that can't be selected.
459 while (static_cast<size_t>(new_selected_line) < names_.size() &&
460 !CanAccept(identifiers()[new_selected_line])) {
464 if (new_selected_line >= static_cast<int>(names_.size()))
465 new_selected_line = 0;
467 SetSelectedLine(new_selected_line);
470 void AutofillPopupControllerImpl::SelectPreviousLine() {
471 int new_selected_line = selected_line_ - 1;
473 // Skip over any lines that can't be selected.
474 while (new_selected_line > kNoSelection &&
475 !CanAccept(identifiers()[new_selected_line])) {
479 if (new_selected_line <= kNoSelection)
480 new_selected_line = names_.size() - 1;
482 SetSelectedLine(new_selected_line);
485 bool AutofillPopupControllerImpl::AcceptSelectedLine() {
486 if (selected_line_ == kNoSelection)
489 DCHECK_GE(selected_line_, 0);
490 DCHECK_LT(selected_line_, static_cast<int>(names_.size()));
492 if (!CanAccept(identifiers_[selected_line_]))
495 AcceptSuggestion(selected_line_);
499 bool AutofillPopupControllerImpl::RemoveSelectedLine() {
500 if (selected_line_ == kNoSelection)
503 DCHECK_GE(selected_line_, 0);
504 DCHECK_LT(selected_line_, static_cast<int>(names_.size()));
506 if (!CanDelete(selected_line_))
509 delegate_->RemoveSuggestion(full_names_[selected_line_],
510 identifiers_[selected_line_]);
512 // Remove the deleted element.
513 names_.erase(names_.begin() + selected_line_);
514 full_names_.erase(full_names_.begin() + selected_line_);
515 subtexts_.erase(subtexts_.begin() + selected_line_);
516 icons_.erase(icons_.begin() + selected_line_);
517 identifiers_.erase(identifiers_.begin() + selected_line_);
519 SetSelectedLine(kNoSelection);
521 if (HasSuggestions()) {
522 delegate_->ClearPreviewedForm();
523 UpdateBoundsAndRedrawPopup();
531 int AutofillPopupControllerImpl::LineFromY(int y) {
532 int current_height = kPopupBorderThickness;
534 for (size_t i = 0; i < identifiers().size(); ++i) {
535 current_height += GetRowHeightFromId(identifiers()[i]);
537 if (y <= current_height)
541 // The y value goes beyond the popup so stop the selection at the last line.
542 return identifiers().size() - 1;
545 int AutofillPopupControllerImpl::GetRowHeightFromId(int identifier) const {
546 if (identifier == WebAutofillClient::MenuItemIDSeparator)
547 return kSeparatorHeight;
552 bool AutofillPopupControllerImpl::CanAccept(int id) {
553 return id != WebAutofillClient::MenuItemIDSeparator &&
554 id != WebAutofillClient::MenuItemIDWarningMessage;
557 bool AutofillPopupControllerImpl::HasSuggestions() {
558 return identifiers_.size() != 0 &&
559 (identifiers_[0] > 0 ||
561 WebAutofillClient::MenuItemIDAutocompleteEntry ||
562 identifiers_[0] == WebAutofillClient::MenuItemIDPasswordEntry ||
563 identifiers_[0] == WebAutofillClient::MenuItemIDDataListEntry);
566 void AutofillPopupControllerImpl::SetValues(
567 const std::vector<base::string16>& names,
568 const std::vector<base::string16>& subtexts,
569 const std::vector<base::string16>& icons,
570 const std::vector<int>& identifiers) {
573 subtexts_ = subtexts;
575 identifiers_ = identifiers;
578 void AutofillPopupControllerImpl::ShowView() {
582 void AutofillPopupControllerImpl::InvalidateRow(size_t row) {
584 DCHECK(row < identifiers_.size());
585 view_->InvalidateRow(row);
588 #if !defined(OS_ANDROID)
589 int AutofillPopupControllerImpl::GetDesiredPopupWidth() const {
590 int popup_width = controller_common_->RoundedElementBounds().width();
591 DCHECK_EQ(names().size(), subtexts().size());
592 for (size_t i = 0; i < names().size(); ++i) {
594 gfx::GetStringWidth(names()[i], name_font_list_) +
595 gfx::GetStringWidth(subtexts()[i], subtext_font_list_) +
596 RowWidthWithoutText(i);
598 popup_width = std::max(popup_width, row_size);
604 int AutofillPopupControllerImpl::GetDesiredPopupHeight() const {
605 int popup_height = 2 * kPopupBorderThickness;
607 for (size_t i = 0; i < identifiers().size(); ++i) {
608 popup_height += GetRowHeightFromId(identifiers()[i]);
614 int AutofillPopupControllerImpl::RowWidthWithoutText(int row) const {
615 int row_size = kEndPadding;
617 if (!subtexts_[row].empty())
618 row_size += kNamePadding;
620 // Add the Autofill icon size, if required.
621 if (!icons_[row].empty()) {
622 int icon_width = ui::ResourceBundle::GetSharedInstance().GetImageNamed(
623 GetIconResourceID(icons_[row])).Width();
624 row_size += icon_width + kIconPadding;
627 // Add the padding at the end.
628 row_size += kEndPadding;
630 // Add room for the popup border.
631 row_size += 2 * kPopupBorderThickness;
636 void AutofillPopupControllerImpl::UpdatePopupBounds() {
637 int popup_required_width = GetDesiredPopupWidth();
638 int popup_height = GetDesiredPopupHeight();
640 popup_bounds_ = controller_common_->GetPopupBounds(popup_height,
641 popup_required_width);
643 #endif // !defined(OS_ANDROID)
645 WeakPtr<AutofillPopupControllerImpl> AutofillPopupControllerImpl::GetWeakPtr() {
646 return weak_ptr_factory_.GetWeakPtr();
649 void AutofillPopupControllerImpl::ClearState() {
650 // Don't clear view_, because otherwise the popup will have to get regenerated
651 // and this will cause flickering.
653 popup_bounds_ = gfx::Rect();
658 identifiers_.clear();
661 selected_line_ = kNoSelection;
664 } // namespace autofill