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 "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"
31 // Used to indicate that no line is currently selected by the user.
32 const int kNoSelection
= -1;
34 // The vertical height of each row in pixels.
35 const size_t kRowHeight
= 24;
37 // The vertical height of a separator in pixels.
38 const size_t kSeparatorHeight
= 1;
40 #if !defined(OS_ANDROID)
41 // Size difference between name and subtext in pixels.
42 const int kLabelFontSizeDelta
= -2;
44 const size_t kNamePadding
= AutofillPopupView::kNamePadding
;
45 const size_t kIconPadding
= AutofillPopupView::kIconPadding
;
46 const size_t kEndPadding
= AutofillPopupView::kEndPadding
;
54 const 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
},
67 WeakPtr
<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());
76 if (previous
.get() && previous
->web_contents() == web_contents
&&
77 previous
->container_view() == container_view
&&
78 previous
->element_bounds() == element_bounds
) {
79 previous
->ClearState();
86 AutofillPopupControllerImpl
* controller
=
87 new AutofillPopupControllerImpl(
88 delegate
, web_contents
, container_view
, element_bounds
,
90 return controller
->GetWeakPtr();
93 AutofillPopupControllerImpl::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
,
104 text_direction_(text_direction
),
105 hide_on_outside_click_(false),
106 weak_ptr_factory_(this) {
108 controller_common_
->SetKeyPressCallback(
109 base::Bind(&AutofillPopupControllerImpl::HandleKeyPressEvent
,
110 base::Unretained(this)));
111 #if !defined(OS_ANDROID)
112 subtext_font_list_
= name_font_list_
.DeriveWithSizeDelta(kLabelFontSizeDelta
);
113 #if defined(OS_MACOSX)
114 // There is no italic version of the system font.
115 warning_font_list_
= name_font_list_
;
117 warning_font_list_
= name_font_list_
.DeriveWithStyle(gfx::Font::ITALIC
);
122 AutofillPopupControllerImpl::~AutofillPopupControllerImpl() {}
124 void AutofillPopupControllerImpl::Show(
125 const std::vector
<base::string16
>& names
,
126 const std::vector
<base::string16
>& subtexts
,
127 const std::vector
<base::string16
>& icons
,
128 const std::vector
<int>& identifiers
) {
129 SetValues(names
, subtexts
, icons
, identifiers
);
131 #if !defined(OS_ANDROID)
132 // Android displays the long text with ellipsis using the view attributes.
135 int popup_width
= popup_bounds().width();
137 // Elide the name and subtext strings so that the popup fits in the available
139 for (size_t i
= 0; i
< names_
.size(); ++i
) {
140 int name_width
= gfx::GetStringWidth(names_
[i
], GetNameFontListForRow(i
));
141 int subtext_width
= gfx::GetStringWidth(subtexts_
[i
], subtext_font_list());
142 int total_text_length
= name_width
+ subtext_width
;
144 // The line can have no strings if it represents a UI element, such as
146 if (total_text_length
== 0)
149 int available_width
= popup_width
- RowWidthWithoutText(i
);
151 // Each field recieves space in proportion to its length.
152 int name_size
= available_width
* name_width
/ total_text_length
;
153 names_
[i
] = gfx::ElideText(names_
[i
],
154 GetNameFontListForRow(i
),
158 int subtext_size
= available_width
* subtext_width
/ total_text_length
;
159 subtexts_
[i
] = gfx::ElideText(subtexts_
[i
],
167 view_
= AutofillPopupView::Create(this);
169 // It is possible to fail to create the popup, in this case
170 // treat the popup as hiding right away.
178 UpdateBoundsAndRedrawPopup();
181 delegate_
->OnPopupShown();
182 controller_common_
->RegisterKeyPressCallback();
185 void AutofillPopupControllerImpl::UpdateDataListValues(
186 const std::vector
<base::string16
>& values
,
187 const std::vector
<base::string16
>& labels
) {
188 // Remove all the old data list values, which should always be at the top of
189 // the list if they are present.
190 while (!identifiers_
.empty() &&
191 identifiers_
[0] == POPUP_ITEM_ID_DATALIST_ENTRY
) {
192 names_
.erase(names_
.begin());
193 subtexts_
.erase(subtexts_
.begin());
194 icons_
.erase(icons_
.begin());
195 identifiers_
.erase(identifiers_
.begin());
198 // If there are no new data list values, exit (clearing the separator if there
200 if (values
.empty()) {
201 if (!identifiers_
.empty() && identifiers_
[0] == POPUP_ITEM_ID_SEPARATOR
) {
202 names_
.erase(names_
.begin());
203 subtexts_
.erase(subtexts_
.begin());
204 icons_
.erase(icons_
.begin());
205 identifiers_
.erase(identifiers_
.begin());
208 // The popup contents have changed, so either update the bounds or hide it.
209 if (HasSuggestions())
210 UpdateBoundsAndRedrawPopup();
217 // Add a separator if there are any other values.
218 if (!identifiers_
.empty() && identifiers_
[0] != POPUP_ITEM_ID_SEPARATOR
) {
219 names_
.insert(names_
.begin(), base::string16());
220 subtexts_
.insert(subtexts_
.begin(), base::string16());
221 icons_
.insert(icons_
.begin(), base::string16());
222 identifiers_
.insert(identifiers_
.begin(), POPUP_ITEM_ID_SEPARATOR
);
226 names_
.insert(names_
.begin(), values
.begin(), values
.end());
227 subtexts_
.insert(subtexts_
.begin(), labels
.begin(), labels
.end());
229 // Add the values that are the same for all data list elements.
230 icons_
.insert(icons_
.begin(), values
.size(), base::string16());
232 identifiers_
.begin(), values
.size(), POPUP_ITEM_ID_DATALIST_ENTRY
);
234 UpdateBoundsAndRedrawPopup();
237 void AutofillPopupControllerImpl::Hide() {
238 controller_common_
->RemoveKeyPressCallback();
240 delegate_
->OnPopupHidden();
248 void AutofillPopupControllerImpl::ViewDestroyed() {
249 // The view has already been destroyed so clear the reference to it.
255 bool AutofillPopupControllerImpl::HandleKeyPressEvent(
256 const content::NativeWebKeyboardEvent
& event
) {
257 switch (event
.windowsKeyCode
) {
259 SelectPreviousLine();
264 case ui::VKEY_PRIOR
: // Page up.
267 case ui::VKEY_NEXT
: // Page down.
268 SetSelectedLine(names().size() - 1);
270 case ui::VKEY_ESCAPE
:
273 case ui::VKEY_DELETE
:
274 return (event
.modifiers
& content::NativeWebKeyboardEvent::ShiftKey
) &&
275 RemoveSelectedLine();
277 // A tab press should cause the selected line to be accepted, but still
278 // return false so the tab key press propagates and changes the cursor
280 AcceptSelectedLine();
282 case ui::VKEY_RETURN
:
283 return AcceptSelectedLine();
289 void AutofillPopupControllerImpl::UpdateBoundsAndRedrawPopup() {
290 #if !defined(OS_ANDROID)
291 // TODO(csharp): Since UpdatePopupBounds can change the position of the popup,
292 // the popup could end up jumping from above the element to below it.
293 // It is unclear if it is better to keep the popup where it was, or if it
294 // should try and move to its desired position.
298 view_
->UpdateBoundsAndRedrawPopup();
301 void AutofillPopupControllerImpl::SetSelectionAtPoint(const gfx::Point
& point
) {
302 SetSelectedLine(LineFromY(point
.y()));
305 bool AutofillPopupControllerImpl::AcceptSelectedLine() {
306 if (selected_line_
== kNoSelection
)
309 DCHECK_GE(selected_line_
, 0);
310 DCHECK_LT(selected_line_
, static_cast<int>(names_
.size()));
312 if (!CanAccept(identifiers_
[selected_line_
]))
315 AcceptSuggestion(selected_line_
);
319 void AutofillPopupControllerImpl::SelectionCleared() {
320 SetSelectedLine(kNoSelection
);
323 bool AutofillPopupControllerImpl::ShouldRepostEvent(
324 const ui::MouseEvent
& event
) {
325 return delegate_
->ShouldRepostEvent(event
);
328 bool AutofillPopupControllerImpl::ShouldHideOnOutsideClick() const {
329 return hide_on_outside_click_
;
332 void AutofillPopupControllerImpl::AcceptSuggestion(size_t index
) {
333 delegate_
->DidAcceptSuggestion(full_names_
[index
], identifiers_
[index
]);
336 int AutofillPopupControllerImpl::GetIconResourceID(
337 const base::string16
& resource_name
) const {
338 for (size_t i
= 0; i
< arraysize(kDataResources
); ++i
) {
339 if (resource_name
== base::ASCIIToUTF16(kDataResources
[i
].name
))
340 return kDataResources
[i
].id
;
346 bool AutofillPopupControllerImpl::CanDelete(size_t index
) const {
347 // TODO(isherman): Native AddressBook suggestions on Mac and Android should
348 // not be considered to be deleteable.
349 int id
= identifiers_
[index
];
350 return id
> 0 || id
== POPUP_ITEM_ID_AUTOCOMPLETE_ENTRY
||
351 id
== POPUP_ITEM_ID_PASSWORD_ENTRY
;
354 bool AutofillPopupControllerImpl::IsWarning(size_t index
) const {
355 return identifiers_
[index
] == POPUP_ITEM_ID_WARNING_MESSAGE
;
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
] == POPUP_ITEM_ID_WARNING_MESSAGE
)
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(names_
[selected_line_
],
451 identifiers_
[selected_line_
]);
453 delegate_
->ClearPreviewedForm();
457 void AutofillPopupControllerImpl::SelectNextLine() {
458 int new_selected_line
= selected_line_
+ 1;
460 // Skip over any lines that can't be selected.
461 while (static_cast<size_t>(new_selected_line
) < names_
.size() &&
462 !CanAccept(identifiers()[new_selected_line
])) {
466 if (new_selected_line
>= static_cast<int>(names_
.size()))
467 new_selected_line
= 0;
469 SetSelectedLine(new_selected_line
);
472 void AutofillPopupControllerImpl::SelectPreviousLine() {
473 int new_selected_line
= selected_line_
- 1;
475 // Skip over any lines that can't be selected.
476 while (new_selected_line
> kNoSelection
&&
477 !CanAccept(identifiers()[new_selected_line
])) {
481 if (new_selected_line
<= kNoSelection
)
482 new_selected_line
= names_
.size() - 1;
484 SetSelectedLine(new_selected_line
);
487 bool AutofillPopupControllerImpl::RemoveSelectedLine() {
488 if (selected_line_
== kNoSelection
)
491 DCHECK_GE(selected_line_
, 0);
492 DCHECK_LT(selected_line_
, static_cast<int>(names_
.size()));
494 if (!CanDelete(selected_line_
))
497 delegate_
->RemoveSuggestion(full_names_
[selected_line_
],
498 identifiers_
[selected_line_
]);
500 // Remove the deleted element.
501 names_
.erase(names_
.begin() + selected_line_
);
502 full_names_
.erase(full_names_
.begin() + selected_line_
);
503 subtexts_
.erase(subtexts_
.begin() + selected_line_
);
504 icons_
.erase(icons_
.begin() + selected_line_
);
505 identifiers_
.erase(identifiers_
.begin() + selected_line_
);
507 SetSelectedLine(kNoSelection
);
509 if (HasSuggestions()) {
510 delegate_
->ClearPreviewedForm();
511 UpdateBoundsAndRedrawPopup();
519 int AutofillPopupControllerImpl::LineFromY(int y
) {
520 int current_height
= kPopupBorderThickness
;
522 for (size_t i
= 0; i
< identifiers().size(); ++i
) {
523 current_height
+= GetRowHeightFromId(identifiers()[i
]);
525 if (y
<= current_height
)
529 // The y value goes beyond the popup so stop the selection at the last line.
530 return identifiers().size() - 1;
533 int AutofillPopupControllerImpl::GetRowHeightFromId(int identifier
) const {
534 if (identifier
== POPUP_ITEM_ID_SEPARATOR
)
535 return kSeparatorHeight
;
540 bool AutofillPopupControllerImpl::CanAccept(int id
) {
541 return id
!= POPUP_ITEM_ID_SEPARATOR
&& id
!= POPUP_ITEM_ID_WARNING_MESSAGE
;
544 bool AutofillPopupControllerImpl::HasSuggestions() {
545 return identifiers_
.size() != 0 &&
546 (identifiers_
[0] > 0 ||
547 identifiers_
[0] == POPUP_ITEM_ID_AUTOCOMPLETE_ENTRY
||
548 identifiers_
[0] == POPUP_ITEM_ID_PASSWORD_ENTRY
||
549 identifiers_
[0] == POPUP_ITEM_ID_DATALIST_ENTRY
);
552 void AutofillPopupControllerImpl::SetValues(
553 const std::vector
<base::string16
>& names
,
554 const std::vector
<base::string16
>& subtexts
,
555 const std::vector
<base::string16
>& icons
,
556 const std::vector
<int>& identifiers
) {
559 subtexts_
= subtexts
;
561 identifiers_
= identifiers
;
564 void AutofillPopupControllerImpl::ShowView() {
568 void AutofillPopupControllerImpl::InvalidateRow(size_t row
) {
570 DCHECK(row
< identifiers_
.size());
571 view_
->InvalidateRow(row
);
574 #if !defined(OS_ANDROID)
575 int AutofillPopupControllerImpl::GetDesiredPopupWidth() const {
576 int popup_width
= controller_common_
->RoundedElementBounds().width();
577 DCHECK_EQ(names().size(), subtexts().size());
578 for (size_t i
= 0; i
< names().size(); ++i
) {
580 gfx::GetStringWidth(names()[i
], name_font_list_
) +
581 gfx::GetStringWidth(subtexts()[i
], subtext_font_list_
) +
582 RowWidthWithoutText(i
);
584 popup_width
= std::max(popup_width
, row_size
);
590 int AutofillPopupControllerImpl::GetDesiredPopupHeight() const {
591 int popup_height
= 2 * kPopupBorderThickness
;
593 for (size_t i
= 0; i
< identifiers().size(); ++i
) {
594 popup_height
+= GetRowHeightFromId(identifiers()[i
]);
600 int AutofillPopupControllerImpl::RowWidthWithoutText(int row
) const {
601 int row_size
= kEndPadding
;
603 if (!subtexts_
[row
].empty())
604 row_size
+= kNamePadding
;
606 // Add the Autofill icon size, if required.
607 if (!icons_
[row
].empty()) {
608 int icon_width
= ui::ResourceBundle::GetSharedInstance().GetImageNamed(
609 GetIconResourceID(icons_
[row
])).Width();
610 row_size
+= icon_width
+ kIconPadding
;
613 // Add the padding at the end.
614 row_size
+= kEndPadding
;
616 // Add room for the popup border.
617 row_size
+= 2 * kPopupBorderThickness
;
622 void AutofillPopupControllerImpl::UpdatePopupBounds() {
623 int popup_required_width
= GetDesiredPopupWidth();
624 int popup_height
= GetDesiredPopupHeight();
626 popup_bounds_
= controller_common_
->GetPopupBounds(popup_height
,
627 popup_required_width
);
629 #endif // !defined(OS_ANDROID)
631 WeakPtr
<AutofillPopupControllerImpl
> AutofillPopupControllerImpl::GetWeakPtr() {
632 return weak_ptr_factory_
.GetWeakPtr();
635 void AutofillPopupControllerImpl::ClearState() {
636 // Don't clear view_, because otherwise the popup will have to get regenerated
637 // and this will cause flickering.
639 popup_bounds_
= gfx::Rect();
644 identifiers_
.clear();
647 selected_line_
= kNoSelection
;
650 } // namespace autofill