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_.DeriveWithSizeDelta(kLabelFontSizeDelta);
114 #if defined(OS_MACOSX)
115 // There is no italic version of the system font.
116 warning_font_list_ = name_font_list_;
118 warning_font_list_ = name_font_list_.DeriveWithStyle(gfx::Font::ITALIC);
123 AutofillPopupControllerImpl::~AutofillPopupControllerImpl() {}
125 void AutofillPopupControllerImpl::Show(
126 const std::vector<base::string16>& names,
127 const std::vector<base::string16>& subtexts,
128 const std::vector<base::string16>& icons,
129 const std::vector<int>& identifiers) {
130 SetValues(names, subtexts, icons, identifiers);
132 #if !defined(OS_ANDROID)
133 // Android displays the long text with ellipsis using the view attributes.
136 int popup_width = popup_bounds().width();
138 // Elide the name and subtext strings so that the popup fits in the available
140 for (size_t i = 0; i < names_.size(); ++i) {
141 int name_width = gfx::GetStringWidth(names_[i], GetNameFontListForRow(i));
142 int subtext_width = gfx::GetStringWidth(subtexts_[i], subtext_font_list());
143 int total_text_length = name_width + subtext_width;
145 // The line can have no strings if it represents a UI element, such as
147 if (total_text_length == 0)
150 int available_width = popup_width - RowWidthWithoutText(i);
152 // Each field recieves space in proportion to its length.
153 int name_size = available_width * name_width / total_text_length;
154 names_[i] = gfx::ElideText(names_[i],
155 GetNameFontListForRow(i),
159 int subtext_size = available_width * subtext_width / total_text_length;
160 subtexts_[i] = gfx::ElideText(subtexts_[i],
168 view_ = AutofillPopupView::Create(this);
170 // It is possible to fail to create the popup, in this case
171 // treat the popup as hiding right away.
179 UpdateBoundsAndRedrawPopup();
182 delegate_->OnPopupShown();
183 controller_common_->RegisterKeyPressCallback();
186 void AutofillPopupControllerImpl::UpdateDataListValues(
187 const std::vector<base::string16>& values,
188 const std::vector<base::string16>& labels) {
189 // Remove all the old data list values, which should always be at the top of
190 // the list if they are present.
191 while (!identifiers_.empty() &&
192 identifiers_[0] == WebAutofillClient::MenuItemIDDataListEntry) {
193 names_.erase(names_.begin());
194 subtexts_.erase(subtexts_.begin());
195 icons_.erase(icons_.begin());
196 identifiers_.erase(identifiers_.begin());
199 // If there are no new data list values, exit (clearing the separator if there
201 if (values.empty()) {
202 if (!identifiers_.empty() &&
203 identifiers_[0] == WebAutofillClient::MenuItemIDSeparator) {
204 names_.erase(names_.begin());
205 subtexts_.erase(subtexts_.begin());
206 icons_.erase(icons_.begin());
207 identifiers_.erase(identifiers_.begin());
210 // The popup contents have changed, so either update the bounds or hide it.
211 if (HasSuggestions())
212 UpdateBoundsAndRedrawPopup();
219 // Add a separator if there are any other values.
220 if (!identifiers_.empty() &&
221 identifiers_[0] != WebAutofillClient::MenuItemIDSeparator) {
222 names_.insert(names_.begin(), base::string16());
223 subtexts_.insert(subtexts_.begin(), base::string16());
224 icons_.insert(icons_.begin(), base::string16());
225 identifiers_.insert(identifiers_.begin(),
226 WebAutofillClient::MenuItemIDSeparator);
230 names_.insert(names_.begin(), values.begin(), values.end());
231 subtexts_.insert(subtexts_.begin(), labels.begin(), labels.end());
233 // Add the values that are the same for all data list elements.
234 icons_.insert(icons_.begin(), values.size(), base::string16());
235 identifiers_.insert(identifiers_.begin(),
237 WebAutofillClient::MenuItemIDDataListEntry);
239 UpdateBoundsAndRedrawPopup();
242 void AutofillPopupControllerImpl::Hide() {
243 controller_common_->RemoveKeyPressCallback();
245 delegate_->OnPopupHidden();
253 void AutofillPopupControllerImpl::ViewDestroyed() {
254 // The view has already been destroyed so clear the reference to it.
260 bool AutofillPopupControllerImpl::HandleKeyPressEvent(
261 const content::NativeWebKeyboardEvent& event) {
262 switch (event.windowsKeyCode) {
264 SelectPreviousLine();
269 case ui::VKEY_PRIOR: // Page up.
272 case ui::VKEY_NEXT: // Page down.
273 SetSelectedLine(names().size() - 1);
275 case ui::VKEY_ESCAPE:
278 case ui::VKEY_DELETE:
279 return (event.modifiers & content::NativeWebKeyboardEvent::ShiftKey) &&
280 RemoveSelectedLine();
282 // A tab press should cause the selected line to be accepted, but still
283 // return false so the tab key press propagates and changes the cursor
285 AcceptSelectedLine();
287 case ui::VKEY_RETURN:
288 return AcceptSelectedLine();
294 void AutofillPopupControllerImpl::UpdateBoundsAndRedrawPopup() {
295 #if !defined(OS_ANDROID)
296 // TODO(csharp): Since UpdatePopupBounds can change the position of the popup,
297 // the popup could end up jumping from above the element to below it.
298 // It is unclear if it is better to keep the popup where it was, or if it
299 // should try and move to its desired position.
303 view_->UpdateBoundsAndRedrawPopup();
306 void AutofillPopupControllerImpl::SetSelectionAtPoint(const gfx::Point& point) {
307 SetSelectedLine(LineFromY(point.y()));
310 void AutofillPopupControllerImpl::AcceptSelectionAtPoint(
311 const gfx::Point& point) {
312 SetSelectionAtPoint(point);
313 AcceptSelectedLine();
316 void AutofillPopupControllerImpl::SelectionCleared() {
317 SetSelectedLine(kNoSelection);
320 bool AutofillPopupControllerImpl::ShouldRepostEvent(
321 const ui::MouseEvent& event) {
322 return delegate_->ShouldRepostEvent(event);
325 bool AutofillPopupControllerImpl::ShouldHideOnOutsideClick() const {
326 return hide_on_outside_click_;
329 void AutofillPopupControllerImpl::AcceptSuggestion(size_t index) {
330 delegate_->DidAcceptSuggestion(full_names_[index], identifiers_[index]);
333 int AutofillPopupControllerImpl::GetIconResourceID(
334 const base::string16& resource_name) const {
335 for (size_t i = 0; i < arraysize(kDataResources); ++i) {
336 if (resource_name == base::ASCIIToUTF16(kDataResources[i].name))
337 return kDataResources[i].id;
343 bool AutofillPopupControllerImpl::CanDelete(size_t index) const {
344 // TODO(isherman): Native AddressBook suggestions on Mac and Android should
345 // not be considered to be deleteable.
346 int id = identifiers_[index];
348 id == WebAutofillClient::MenuItemIDAutocompleteEntry ||
349 id == WebAutofillClient::MenuItemIDPasswordEntry;
352 bool AutofillPopupControllerImpl::IsWarning(size_t index) const {
353 return identifiers_[index] == WebAutofillClient::MenuItemIDWarningMessage;
356 gfx::Rect AutofillPopupControllerImpl::GetRowBounds(size_t index) {
357 int top = kPopupBorderThickness;
358 for (size_t i = 0; i < index; ++i) {
359 top += GetRowHeightFromId(identifiers()[i]);
363 kPopupBorderThickness,
365 popup_bounds_.width() - 2 * kPopupBorderThickness,
366 GetRowHeightFromId(identifiers()[index]));
369 void AutofillPopupControllerImpl::SetPopupBounds(const gfx::Rect& bounds) {
370 popup_bounds_ = bounds;
371 UpdateBoundsAndRedrawPopup();
374 const gfx::Rect& AutofillPopupControllerImpl::popup_bounds() const {
375 return popup_bounds_;
378 content::WebContents* AutofillPopupControllerImpl::web_contents() {
379 return controller_common_->web_contents();
382 gfx::NativeView AutofillPopupControllerImpl::container_view() {
383 return controller_common_->container_view();
386 const gfx::RectF& AutofillPopupControllerImpl::element_bounds() const {
387 return controller_common_->element_bounds();
390 bool AutofillPopupControllerImpl::IsRTL() const {
391 return text_direction_ == base::i18n::RIGHT_TO_LEFT;
394 const std::vector<base::string16>& AutofillPopupControllerImpl::names() const {
398 const std::vector<base::string16>& AutofillPopupControllerImpl::subtexts()
403 const std::vector<base::string16>& AutofillPopupControllerImpl::icons() const {
407 const std::vector<int>& AutofillPopupControllerImpl::identifiers() const {
411 #if !defined(OS_ANDROID)
412 const gfx::FontList& AutofillPopupControllerImpl::GetNameFontListForRow(
413 size_t index) const {
414 if (identifiers_[index] == WebAutofillClient::MenuItemIDWarningMessage)
415 return warning_font_list_;
417 return name_font_list_;
420 const gfx::FontList& AutofillPopupControllerImpl::subtext_font_list() const {
421 return subtext_font_list_;
425 int AutofillPopupControllerImpl::selected_line() const {
426 return selected_line_;
429 void AutofillPopupControllerImpl::set_hide_on_outside_click(
430 bool hide_on_outside_click) {
431 hide_on_outside_click_ = hide_on_outside_click;
434 void AutofillPopupControllerImpl::SetSelectedLine(int selected_line) {
435 if (selected_line_ == selected_line)
438 if (selected_line_ != kNoSelection &&
439 static_cast<size_t>(selected_line_) < identifiers_.size())
440 InvalidateRow(selected_line_);
442 if (selected_line != kNoSelection)
443 InvalidateRow(selected_line);
445 selected_line_ = selected_line;
447 if (selected_line_ != kNoSelection)
448 delegate_->DidSelectSuggestion(identifiers_[selected_line_]);
450 delegate_->ClearPreviewedForm();
453 void AutofillPopupControllerImpl::SelectNextLine() {
454 int new_selected_line = selected_line_ + 1;
456 // Skip over any lines that can't be selected.
457 while (static_cast<size_t>(new_selected_line) < names_.size() &&
458 !CanAccept(identifiers()[new_selected_line])) {
462 if (new_selected_line >= static_cast<int>(names_.size()))
463 new_selected_line = 0;
465 SetSelectedLine(new_selected_line);
468 void AutofillPopupControllerImpl::SelectPreviousLine() {
469 int new_selected_line = selected_line_ - 1;
471 // Skip over any lines that can't be selected.
472 while (new_selected_line > kNoSelection &&
473 !CanAccept(identifiers()[new_selected_line])) {
477 if (new_selected_line <= kNoSelection)
478 new_selected_line = names_.size() - 1;
480 SetSelectedLine(new_selected_line);
483 bool AutofillPopupControllerImpl::AcceptSelectedLine() {
484 if (selected_line_ == kNoSelection)
487 DCHECK_GE(selected_line_, 0);
488 DCHECK_LT(selected_line_, static_cast<int>(names_.size()));
490 if (!CanAccept(identifiers_[selected_line_]))
493 AcceptSuggestion(selected_line_);
497 bool AutofillPopupControllerImpl::RemoveSelectedLine() {
498 if (selected_line_ == kNoSelection)
501 DCHECK_GE(selected_line_, 0);
502 DCHECK_LT(selected_line_, static_cast<int>(names_.size()));
504 if (!CanDelete(selected_line_))
507 delegate_->RemoveSuggestion(full_names_[selected_line_],
508 identifiers_[selected_line_]);
510 // Remove the deleted element.
511 names_.erase(names_.begin() + selected_line_);
512 full_names_.erase(full_names_.begin() + selected_line_);
513 subtexts_.erase(subtexts_.begin() + selected_line_);
514 icons_.erase(icons_.begin() + selected_line_);
515 identifiers_.erase(identifiers_.begin() + selected_line_);
517 SetSelectedLine(kNoSelection);
519 if (HasSuggestions()) {
520 delegate_->ClearPreviewedForm();
521 UpdateBoundsAndRedrawPopup();
529 int AutofillPopupControllerImpl::LineFromY(int y) {
530 int current_height = kPopupBorderThickness;
532 for (size_t i = 0; i < identifiers().size(); ++i) {
533 current_height += GetRowHeightFromId(identifiers()[i]);
535 if (y <= current_height)
539 // The y value goes beyond the popup so stop the selection at the last line.
540 return identifiers().size() - 1;
543 int AutofillPopupControllerImpl::GetRowHeightFromId(int identifier) const {
544 if (identifier == WebAutofillClient::MenuItemIDSeparator)
545 return kSeparatorHeight;
550 bool AutofillPopupControllerImpl::CanAccept(int id) {
551 return id != WebAutofillClient::MenuItemIDSeparator &&
552 id != WebAutofillClient::MenuItemIDWarningMessage;
555 bool AutofillPopupControllerImpl::HasSuggestions() {
556 return identifiers_.size() != 0 &&
557 (identifiers_[0] > 0 ||
559 WebAutofillClient::MenuItemIDAutocompleteEntry ||
560 identifiers_[0] == WebAutofillClient::MenuItemIDPasswordEntry ||
561 identifiers_[0] == WebAutofillClient::MenuItemIDDataListEntry);
564 void AutofillPopupControllerImpl::SetValues(
565 const std::vector<base::string16>& names,
566 const std::vector<base::string16>& subtexts,
567 const std::vector<base::string16>& icons,
568 const std::vector<int>& identifiers) {
571 subtexts_ = subtexts;
573 identifiers_ = identifiers;
576 void AutofillPopupControllerImpl::ShowView() {
580 void AutofillPopupControllerImpl::InvalidateRow(size_t row) {
582 DCHECK(row < identifiers_.size());
583 view_->InvalidateRow(row);
586 #if !defined(OS_ANDROID)
587 int AutofillPopupControllerImpl::GetDesiredPopupWidth() const {
588 int popup_width = controller_common_->RoundedElementBounds().width();
589 DCHECK_EQ(names().size(), subtexts().size());
590 for (size_t i = 0; i < names().size(); ++i) {
592 gfx::GetStringWidth(names()[i], name_font_list_) +
593 gfx::GetStringWidth(subtexts()[i], subtext_font_list_) +
594 RowWidthWithoutText(i);
596 popup_width = std::max(popup_width, row_size);
602 int AutofillPopupControllerImpl::GetDesiredPopupHeight() const {
603 int popup_height = 2 * kPopupBorderThickness;
605 for (size_t i = 0; i < identifiers().size(); ++i) {
606 popup_height += GetRowHeightFromId(identifiers()[i]);
612 int AutofillPopupControllerImpl::RowWidthWithoutText(int row) const {
613 int row_size = kEndPadding;
615 if (!subtexts_[row].empty())
616 row_size += kNamePadding;
618 // Add the Autofill icon size, if required.
619 if (!icons_[row].empty()) {
620 int icon_width = ui::ResourceBundle::GetSharedInstance().GetImageNamed(
621 GetIconResourceID(icons_[row])).Width();
622 row_size += icon_width + kIconPadding;
625 // Add the padding at the end.
626 row_size += kEndPadding;
628 // Add room for the popup border.
629 row_size += 2 * kPopupBorderThickness;
634 void AutofillPopupControllerImpl::UpdatePopupBounds() {
635 int popup_required_width = GetDesiredPopupWidth();
636 int popup_height = GetDesiredPopupHeight();
638 popup_bounds_ = controller_common_->GetPopupBounds(popup_height,
639 popup_required_width);
641 #endif // !defined(OS_ANDROID)
643 WeakPtr<AutofillPopupControllerImpl> AutofillPopupControllerImpl::GetWeakPtr() {
644 return weak_ptr_factory_.GetWeakPtr();
647 void AutofillPopupControllerImpl::ClearState() {
648 // Don't clear view_, because otherwise the popup will have to get regenerated
649 // and this will cause flickering.
651 popup_bounds_ = gfx::Rect();
656 identifiers_.clear();
659 selected_line_ = kNoSelection;
662 } // namespace autofill