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 weak_ptr_factory_(this) {
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_
;
116 warning_font_list_
= name_font_list_
.DeriveWithStyle(gfx::Font::ITALIC
);
121 AutofillPopupControllerImpl::~AutofillPopupControllerImpl() {}
123 void 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
);
130 #if !defined(OS_ANDROID)
131 // Android displays the long text with ellipsis using the view attributes.
134 int popup_width
= popup_bounds().width();
136 // Elide the name and subtext strings so that the popup fits in the available
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
;
143 // The line can have no strings if it represents a UI element, such as
145 if (total_text_length
== 0)
148 int available_width
= popup_width
- RowWidthWithoutText(i
);
150 // Each field receives 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
),
157 int subtext_size
= available_width
* subtext_width
/ total_text_length
;
158 subtexts_
[i
] = gfx::ElideText(subtexts_
[i
],
166 view_
= AutofillPopupView::Create(this);
168 // It is possible to fail to create the popup, in this case
169 // treat the popup as hiding right away.
177 UpdateBoundsAndRedrawPopup();
180 controller_common_
->RegisterKeyPressCallback();
181 delegate_
->OnPopupShown();
184 void 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());
197 // If there are no new data list values, exit (clearing the separator if there
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());
207 // The popup contents have changed, so either update the bounds or hide it.
208 if (HasSuggestions())
209 UpdateBoundsAndRedrawPopup();
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
);
225 names_
.insert(names_
.begin(), values
.begin(), values
.end());
226 subtexts_
.insert(subtexts_
.begin(), labels
.begin(), labels
.end());
228 // Add the values that are the same for all data list elements.
229 icons_
.insert(icons_
.begin(), values
.size(), base::string16());
231 identifiers_
.begin(), values
.size(), POPUP_ITEM_ID_DATALIST_ENTRY
);
233 UpdateBoundsAndRedrawPopup();
236 void AutofillPopupControllerImpl::Hide() {
237 controller_common_
->RemoveKeyPressCallback();
239 delegate_
->OnPopupHidden();
247 void AutofillPopupControllerImpl::ViewDestroyed() {
248 // The view has already been destroyed so clear the reference to it.
254 bool AutofillPopupControllerImpl::HandleKeyPressEvent(
255 const content::NativeWebKeyboardEvent
& event
) {
256 switch (event
.windowsKeyCode
) {
258 SelectPreviousLine();
263 case ui::VKEY_PRIOR
: // Page up.
266 case ui::VKEY_NEXT
: // Page down.
267 SetSelectedLine(names().size() - 1);
269 case ui::VKEY_ESCAPE
:
272 case ui::VKEY_DELETE
:
273 return (event
.modifiers
& content::NativeWebKeyboardEvent::ShiftKey
) &&
274 RemoveSelectedLine();
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
279 AcceptSelectedLine();
281 case ui::VKEY_RETURN
:
282 return AcceptSelectedLine();
288 void 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.
297 view_
->UpdateBoundsAndRedrawPopup();
300 void AutofillPopupControllerImpl::SetSelectionAtPoint(const gfx::Point
& point
) {
301 SetSelectedLine(LineFromY(point
.y()));
304 bool AutofillPopupControllerImpl::AcceptSelectedLine() {
305 if (selected_line_
== kNoSelection
)
308 DCHECK_GE(selected_line_
, 0);
309 DCHECK_LT(selected_line_
, static_cast<int>(names_
.size()));
311 if (!CanAccept(identifiers_
[selected_line_
]))
314 AcceptSuggestion(selected_line_
);
318 void AutofillPopupControllerImpl::SelectionCleared() {
319 SetSelectedLine(kNoSelection
);
322 void AutofillPopupControllerImpl::AcceptSuggestion(size_t index
) {
323 delegate_
->DidAcceptSuggestion(full_names_
[index
], identifiers_
[index
]);
326 int 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
;
336 bool 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
;
344 bool AutofillPopupControllerImpl::IsWarning(size_t index
) const {
345 return identifiers_
[index
] == POPUP_ITEM_ID_WARNING_MESSAGE
;
348 gfx::Rect
AutofillPopupControllerImpl::GetRowBounds(size_t index
) {
349 int top
= kPopupBorderThickness
;
350 for (size_t i
= 0; i
< index
; ++i
) {
351 top
+= GetRowHeightFromId(identifiers()[i
]);
355 kPopupBorderThickness
,
357 popup_bounds_
.width() - 2 * kPopupBorderThickness
,
358 GetRowHeightFromId(identifiers()[index
]));
361 void AutofillPopupControllerImpl::SetPopupBounds(const gfx::Rect
& bounds
) {
362 popup_bounds_
= bounds
;
363 UpdateBoundsAndRedrawPopup();
366 const gfx::Rect
& AutofillPopupControllerImpl::popup_bounds() const {
367 return popup_bounds_
;
370 content::WebContents
* AutofillPopupControllerImpl::web_contents() {
371 return controller_common_
->web_contents();
374 gfx::NativeView
AutofillPopupControllerImpl::container_view() {
375 return controller_common_
->container_view();
378 const gfx::RectF
& AutofillPopupControllerImpl::element_bounds() const {
379 return controller_common_
->element_bounds();
382 bool AutofillPopupControllerImpl::IsRTL() const {
383 return text_direction_
== base::i18n::RIGHT_TO_LEFT
;
386 const std::vector
<base::string16
>& AutofillPopupControllerImpl::names() const {
390 const std::vector
<base::string16
>& AutofillPopupControllerImpl::subtexts()
395 const std::vector
<base::string16
>& AutofillPopupControllerImpl::icons() const {
399 const std::vector
<int>& AutofillPopupControllerImpl::identifiers() const {
403 #if !defined(OS_ANDROID)
404 const gfx::FontList
& AutofillPopupControllerImpl::GetNameFontListForRow(
405 size_t index
) const {
406 if (identifiers_
[index
] == POPUP_ITEM_ID_WARNING_MESSAGE
)
407 return warning_font_list_
;
409 return name_font_list_
;
412 const gfx::FontList
& AutofillPopupControllerImpl::subtext_font_list() const {
413 return subtext_font_list_
;
417 int AutofillPopupControllerImpl::selected_line() const {
418 return selected_line_
;
421 void AutofillPopupControllerImpl::SetSelectedLine(int selected_line
) {
422 if (selected_line_
== selected_line
)
425 if (selected_line_
!= kNoSelection
&&
426 static_cast<size_t>(selected_line_
) < identifiers_
.size())
427 InvalidateRow(selected_line_
);
429 if (selected_line
!= kNoSelection
)
430 InvalidateRow(selected_line
);
432 selected_line_
= selected_line
;
434 if (selected_line_
!= kNoSelection
) {
435 delegate_
->DidSelectSuggestion(names_
[selected_line_
],
436 identifiers_
[selected_line_
]);
438 delegate_
->ClearPreviewedForm();
442 void AutofillPopupControllerImpl::SelectNextLine() {
443 int new_selected_line
= selected_line_
+ 1;
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
])) {
451 if (new_selected_line
>= static_cast<int>(names_
.size()))
452 new_selected_line
= 0;
454 SetSelectedLine(new_selected_line
);
457 void AutofillPopupControllerImpl::SelectPreviousLine() {
458 int new_selected_line
= selected_line_
- 1;
460 // Skip over any lines that can't be selected.
461 while (new_selected_line
> kNoSelection
&&
462 !CanAccept(identifiers()[new_selected_line
])) {
466 if (new_selected_line
<= kNoSelection
)
467 new_selected_line
= names_
.size() - 1;
469 SetSelectedLine(new_selected_line
);
472 bool AutofillPopupControllerImpl::RemoveSelectedLine() {
473 if (selected_line_
== kNoSelection
)
476 DCHECK_GE(selected_line_
, 0);
477 DCHECK_LT(selected_line_
, static_cast<int>(names_
.size()));
479 if (!CanDelete(selected_line_
))
482 delegate_
->RemoveSuggestion(full_names_
[selected_line_
],
483 identifiers_
[selected_line_
]);
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_
);
492 SetSelectedLine(kNoSelection
);
494 if (HasSuggestions()) {
495 delegate_
->ClearPreviewedForm();
496 UpdateBoundsAndRedrawPopup();
504 int AutofillPopupControllerImpl::LineFromY(int y
) {
505 int current_height
= kPopupBorderThickness
;
507 for (size_t i
= 0; i
< identifiers().size(); ++i
) {
508 current_height
+= GetRowHeightFromId(identifiers()[i
]);
510 if (y
<= current_height
)
514 // The y value goes beyond the popup so stop the selection at the last line.
515 return identifiers().size() - 1;
518 int AutofillPopupControllerImpl::GetRowHeightFromId(int identifier
) const {
519 if (identifier
== POPUP_ITEM_ID_SEPARATOR
)
520 return kSeparatorHeight
;
525 bool AutofillPopupControllerImpl::CanAccept(int id
) {
526 return id
!= POPUP_ITEM_ID_SEPARATOR
&& id
!= POPUP_ITEM_ID_WARNING_MESSAGE
;
529 bool 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
);
537 void 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
) {
544 subtexts_
= subtexts
;
546 identifiers_
= identifiers
;
549 void AutofillPopupControllerImpl::ShowView() {
553 void AutofillPopupControllerImpl::InvalidateRow(size_t row
) {
555 DCHECK(row
< identifiers_
.size());
556 view_
->InvalidateRow(row
);
559 #if !defined(OS_ANDROID)
560 int 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
) {
565 gfx::GetStringWidth(names()[i
], name_font_list_
) +
566 gfx::GetStringWidth(subtexts()[i
], subtext_font_list_
) +
567 RowWidthWithoutText(i
);
569 popup_width
= std::max(popup_width
, row_size
);
575 int AutofillPopupControllerImpl::GetDesiredPopupHeight() const {
576 int popup_height
= 2 * kPopupBorderThickness
;
578 for (size_t i
= 0; i
< identifiers().size(); ++i
) {
579 popup_height
+= GetRowHeightFromId(identifiers()[i
]);
585 int AutofillPopupControllerImpl::RowWidthWithoutText(int row
) const {
586 int row_size
= kEndPadding
;
588 if (!subtexts_
[row
].empty())
589 row_size
+= kNamePadding
;
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
;
598 // Add the padding at the end.
599 row_size
+= kEndPadding
;
601 // Add room for the popup border.
602 row_size
+= 2 * kPopupBorderThickness
;
607 void AutofillPopupControllerImpl::UpdatePopupBounds() {
608 int popup_required_width
= GetDesiredPopupWidth();
609 int popup_height
= GetDesiredPopupHeight();
611 popup_bounds_
= controller_common_
->GetPopupBounds(popup_height
,
612 popup_required_width
);
614 #endif // !defined(OS_ANDROID)
616 WeakPtr
<AutofillPopupControllerImpl
> AutofillPopupControllerImpl::GetWeakPtr() {
617 return weak_ptr_factory_
.GetWeakPtr();
620 void AutofillPopupControllerImpl::ClearState() {
621 // Don't clear view_, because otherwise the popup will have to get regenerated
622 // and this will cause flickering.
624 popup_bounds_
= gfx::Rect();
629 identifiers_
.clear();
632 selected_line_
= kNoSelection
;
635 } // namespace autofill