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
},
62 #if defined(OS_MACOSX) && !defined(OS_IOS)
63 { "macContactsIcon", IDR_AUTOFILL_MAC_CONTACTS_ICON
},
64 #endif // defined(OS_MACOSX) && !defined(OS_IOS)
70 WeakPtr
<AutofillPopupControllerImpl
> AutofillPopupControllerImpl::GetOrCreate(
71 WeakPtr
<AutofillPopupControllerImpl
> previous
,
72 WeakPtr
<AutofillPopupDelegate
> delegate
,
73 content::WebContents
* web_contents
,
74 gfx::NativeView container_view
,
75 const gfx::RectF
& element_bounds
,
76 base::i18n::TextDirection text_direction
) {
77 DCHECK(!previous
.get() || previous
->delegate_
.get() == delegate
.get());
79 if (previous
.get() && previous
->web_contents() == web_contents
&&
80 previous
->container_view() == container_view
&&
81 previous
->element_bounds() == element_bounds
) {
82 previous
->ClearState();
89 AutofillPopupControllerImpl
* controller
=
90 new AutofillPopupControllerImpl(
91 delegate
, web_contents
, container_view
, element_bounds
,
93 return controller
->GetWeakPtr();
96 AutofillPopupControllerImpl::AutofillPopupControllerImpl(
97 base::WeakPtr
<AutofillPopupDelegate
> delegate
,
98 content::WebContents
* web_contents
,
99 gfx::NativeView container_view
,
100 const gfx::RectF
& element_bounds
,
101 base::i18n::TextDirection text_direction
)
102 : controller_common_(new PopupControllerCommon(element_bounds
,
107 text_direction_(text_direction
),
108 weak_ptr_factory_(this) {
110 controller_common_
->SetKeyPressCallback(
111 base::Bind(&AutofillPopupControllerImpl::HandleKeyPressEvent
,
112 base::Unretained(this)));
113 #if !defined(OS_ANDROID)
114 subtext_font_list_
= name_font_list_
.DeriveWithSizeDelta(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_
.DeriveWithStyle(gfx::Font::ITALIC
);
124 AutofillPopupControllerImpl::~AutofillPopupControllerImpl() {}
126 void AutofillPopupControllerImpl::Show(
127 const std::vector
<base::string16
>& names
,
128 const std::vector
<base::string16
>& subtexts
,
129 const std::vector
<base::string16
>& icons
,
130 const std::vector
<int>& identifiers
) {
131 SetValues(names
, subtexts
, icons
, identifiers
);
133 #if !defined(OS_ANDROID)
134 // Android displays the long text with ellipsis using the view attributes.
137 int popup_width
= popup_bounds().width();
139 // Elide the name and subtext strings so that the popup fits in the available
141 for (size_t i
= 0; i
< names_
.size(); ++i
) {
142 int name_width
= gfx::GetStringWidth(names_
[i
], GetNameFontListForRow(i
));
143 int subtext_width
= gfx::GetStringWidth(subtexts_
[i
], subtext_font_list());
144 int total_text_length
= name_width
+ subtext_width
;
146 // The line can have no strings if it represents a UI element, such as
148 if (total_text_length
== 0)
151 int available_width
= popup_width
- RowWidthWithoutText(i
);
153 // Each field receives space in proportion to its length.
154 int name_size
= available_width
* name_width
/ total_text_length
;
155 names_
[i
] = gfx::ElideText(names_
[i
], GetNameFontListForRow(i
),
156 name_size
, gfx::ELIDE_TAIL
);
158 int subtext_size
= available_width
* subtext_width
/ total_text_length
;
159 subtexts_
[i
] = gfx::ElideText(subtexts_
[i
], subtext_font_list(),
160 subtext_size
, gfx::ELIDE_TAIL
);
165 view_
= AutofillPopupView::Create(this);
167 // It is possible to fail to create the popup, in this case
168 // treat the popup as hiding right away.
176 UpdateBoundsAndRedrawPopup();
179 controller_common_
->RegisterKeyPressCallback();
180 delegate_
->OnPopupShown();
183 void AutofillPopupControllerImpl::UpdateDataListValues(
184 const std::vector
<base::string16
>& values
,
185 const std::vector
<base::string16
>& labels
) {
186 // Remove all the old data list values, which should always be at the top of
187 // the list if they are present.
188 while (!identifiers_
.empty() &&
189 identifiers_
[0] == POPUP_ITEM_ID_DATALIST_ENTRY
) {
190 names_
.erase(names_
.begin());
191 subtexts_
.erase(subtexts_
.begin());
192 icons_
.erase(icons_
.begin());
193 identifiers_
.erase(identifiers_
.begin());
196 // If there are no new data list values, exit (clearing the separator if there
198 if (values
.empty()) {
199 if (!identifiers_
.empty() && identifiers_
[0] == POPUP_ITEM_ID_SEPARATOR
) {
200 names_
.erase(names_
.begin());
201 subtexts_
.erase(subtexts_
.begin());
202 icons_
.erase(icons_
.begin());
203 identifiers_
.erase(identifiers_
.begin());
206 // The popup contents have changed, so either update the bounds or hide it.
207 if (HasSuggestions())
208 UpdateBoundsAndRedrawPopup();
215 // Add a separator if there are any other values.
216 if (!identifiers_
.empty() && identifiers_
[0] != POPUP_ITEM_ID_SEPARATOR
) {
217 names_
.insert(names_
.begin(), base::string16());
218 subtexts_
.insert(subtexts_
.begin(), base::string16());
219 icons_
.insert(icons_
.begin(), base::string16());
220 identifiers_
.insert(identifiers_
.begin(), POPUP_ITEM_ID_SEPARATOR
);
224 names_
.insert(names_
.begin(), values
.begin(), values
.end());
225 subtexts_
.insert(subtexts_
.begin(), labels
.begin(), labels
.end());
227 // Add the values that are the same for all data list elements.
228 icons_
.insert(icons_
.begin(), values
.size(), base::string16());
230 identifiers_
.begin(), values
.size(), POPUP_ITEM_ID_DATALIST_ENTRY
);
232 UpdateBoundsAndRedrawPopup();
235 void AutofillPopupControllerImpl::Hide() {
236 controller_common_
->RemoveKeyPressCallback();
238 delegate_
->OnPopupHidden();
246 void AutofillPopupControllerImpl::ViewDestroyed() {
247 // The view has already been destroyed so clear the reference to it.
253 bool AutofillPopupControllerImpl::HandleKeyPressEvent(
254 const content::NativeWebKeyboardEvent
& event
) {
255 switch (event
.windowsKeyCode
) {
257 SelectPreviousLine();
262 case ui::VKEY_PRIOR
: // Page up.
265 case ui::VKEY_NEXT
: // Page down.
266 SetSelectedLine(names().size() - 1);
268 case ui::VKEY_ESCAPE
:
271 case ui::VKEY_DELETE
:
272 return (event
.modifiers
& content::NativeWebKeyboardEvent::ShiftKey
) &&
273 RemoveSelectedLine();
275 // A tab press should cause the selected line to be accepted, but still
276 // return false so the tab key press propagates and changes the cursor
278 AcceptSelectedLine();
280 case ui::VKEY_RETURN
:
281 return AcceptSelectedLine();
287 void AutofillPopupControllerImpl::UpdateBoundsAndRedrawPopup() {
288 #if !defined(OS_ANDROID)
289 // TODO(csharp): Since UpdatePopupBounds can change the position of the popup,
290 // the popup could end up jumping from above the element to below it.
291 // It is unclear if it is better to keep the popup where it was, or if it
292 // should try and move to its desired position.
296 view_
->UpdateBoundsAndRedrawPopup();
299 void AutofillPopupControllerImpl::SetSelectionAtPoint(const gfx::Point
& point
) {
300 SetSelectedLine(LineFromY(point
.y()));
303 bool AutofillPopupControllerImpl::AcceptSelectedLine() {
304 if (selected_line_
== kNoSelection
)
307 DCHECK_GE(selected_line_
, 0);
308 DCHECK_LT(selected_line_
, static_cast<int>(names_
.size()));
310 if (!CanAccept(identifiers_
[selected_line_
]))
313 AcceptSuggestion(selected_line_
);
317 void AutofillPopupControllerImpl::SelectionCleared() {
318 SetSelectedLine(kNoSelection
);
321 void AutofillPopupControllerImpl::AcceptSuggestion(size_t index
) {
322 delegate_
->DidAcceptSuggestion(full_names_
[index
], identifiers_
[index
]);
325 int AutofillPopupControllerImpl::GetIconResourceID(
326 const base::string16
& resource_name
) const {
327 for (size_t i
= 0; i
< arraysize(kDataResources
); ++i
) {
328 if (resource_name
== base::ASCIIToUTF16(kDataResources
[i
].name
))
329 return kDataResources
[i
].id
;
335 bool AutofillPopupControllerImpl::CanDelete(size_t index
) const {
336 // TODO(isherman): Native AddressBook suggestions on Mac and Android should
337 // not be considered to be deleteable.
338 int id
= identifiers_
[index
];
339 return id
> 0 || id
== POPUP_ITEM_ID_AUTOCOMPLETE_ENTRY
||
340 id
== POPUP_ITEM_ID_PASSWORD_ENTRY
;
343 bool AutofillPopupControllerImpl::IsWarning(size_t index
) const {
344 return identifiers_
[index
] == POPUP_ITEM_ID_WARNING_MESSAGE
;
347 gfx::Rect
AutofillPopupControllerImpl::GetRowBounds(size_t index
) {
348 int top
= kPopupBorderThickness
;
349 for (size_t i
= 0; i
< index
; ++i
) {
350 top
+= GetRowHeightFromId(identifiers()[i
]);
354 kPopupBorderThickness
,
356 popup_bounds_
.width() - 2 * kPopupBorderThickness
,
357 GetRowHeightFromId(identifiers()[index
]));
360 void AutofillPopupControllerImpl::SetPopupBounds(const gfx::Rect
& bounds
) {
361 popup_bounds_
= bounds
;
362 UpdateBoundsAndRedrawPopup();
365 const gfx::Rect
& AutofillPopupControllerImpl::popup_bounds() const {
366 return popup_bounds_
;
369 content::WebContents
* AutofillPopupControllerImpl::web_contents() {
370 return controller_common_
->web_contents();
373 gfx::NativeView
AutofillPopupControllerImpl::container_view() {
374 return controller_common_
->container_view();
377 const gfx::RectF
& AutofillPopupControllerImpl::element_bounds() const {
378 return controller_common_
->element_bounds();
381 bool AutofillPopupControllerImpl::IsRTL() const {
382 return text_direction_
== base::i18n::RIGHT_TO_LEFT
;
385 const std::vector
<base::string16
>& AutofillPopupControllerImpl::names() const {
389 const std::vector
<base::string16
>& AutofillPopupControllerImpl::subtexts()
394 const std::vector
<base::string16
>& AutofillPopupControllerImpl::icons() const {
398 const std::vector
<int>& AutofillPopupControllerImpl::identifiers() const {
402 #if !defined(OS_ANDROID)
403 const gfx::FontList
& AutofillPopupControllerImpl::GetNameFontListForRow(
404 size_t index
) const {
405 if (identifiers_
[index
] == POPUP_ITEM_ID_WARNING_MESSAGE
)
406 return warning_font_list_
;
408 return name_font_list_
;
411 const gfx::FontList
& AutofillPopupControllerImpl::subtext_font_list() const {
412 return subtext_font_list_
;
416 int AutofillPopupControllerImpl::selected_line() const {
417 return selected_line_
;
420 void AutofillPopupControllerImpl::SetSelectedLine(int selected_line
) {
421 if (selected_line_
== selected_line
)
424 if (selected_line_
!= kNoSelection
&&
425 static_cast<size_t>(selected_line_
) < identifiers_
.size())
426 InvalidateRow(selected_line_
);
428 if (selected_line
!= kNoSelection
)
429 InvalidateRow(selected_line
);
431 selected_line_
= selected_line
;
433 if (selected_line_
!= kNoSelection
) {
434 delegate_
->DidSelectSuggestion(names_
[selected_line_
],
435 identifiers_
[selected_line_
]);
437 delegate_
->ClearPreviewedForm();
441 void AutofillPopupControllerImpl::SelectNextLine() {
442 int new_selected_line
= selected_line_
+ 1;
444 // Skip over any lines that can't be selected.
445 while (static_cast<size_t>(new_selected_line
) < names_
.size() &&
446 !CanAccept(identifiers()[new_selected_line
])) {
450 if (new_selected_line
>= static_cast<int>(names_
.size()))
451 new_selected_line
= 0;
453 SetSelectedLine(new_selected_line
);
456 void AutofillPopupControllerImpl::SelectPreviousLine() {
457 int new_selected_line
= selected_line_
- 1;
459 // Skip over any lines that can't be selected.
460 while (new_selected_line
> kNoSelection
&&
461 !CanAccept(identifiers()[new_selected_line
])) {
465 if (new_selected_line
<= kNoSelection
)
466 new_selected_line
= names_
.size() - 1;
468 SetSelectedLine(new_selected_line
);
471 bool AutofillPopupControllerImpl::RemoveSelectedLine() {
472 if (selected_line_
== kNoSelection
)
475 DCHECK_GE(selected_line_
, 0);
476 DCHECK_LT(selected_line_
, static_cast<int>(names_
.size()));
478 if (!CanDelete(selected_line_
))
481 delegate_
->RemoveSuggestion(full_names_
[selected_line_
],
482 identifiers_
[selected_line_
]);
484 // Remove the deleted element.
485 names_
.erase(names_
.begin() + selected_line_
);
486 full_names_
.erase(full_names_
.begin() + selected_line_
);
487 subtexts_
.erase(subtexts_
.begin() + selected_line_
);
488 icons_
.erase(icons_
.begin() + selected_line_
);
489 identifiers_
.erase(identifiers_
.begin() + selected_line_
);
491 SetSelectedLine(kNoSelection
);
493 if (HasSuggestions()) {
494 delegate_
->ClearPreviewedForm();
495 UpdateBoundsAndRedrawPopup();
503 int AutofillPopupControllerImpl::LineFromY(int y
) {
504 int current_height
= kPopupBorderThickness
;
506 for (size_t i
= 0; i
< identifiers().size(); ++i
) {
507 current_height
+= GetRowHeightFromId(identifiers()[i
]);
509 if (y
<= current_height
)
513 // The y value goes beyond the popup so stop the selection at the last line.
514 return identifiers().size() - 1;
517 int AutofillPopupControllerImpl::GetRowHeightFromId(int identifier
) const {
518 if (identifier
== POPUP_ITEM_ID_SEPARATOR
)
519 return kSeparatorHeight
;
524 bool AutofillPopupControllerImpl::CanAccept(int id
) {
525 return id
!= POPUP_ITEM_ID_SEPARATOR
&& id
!= POPUP_ITEM_ID_WARNING_MESSAGE
;
528 bool AutofillPopupControllerImpl::HasSuggestions() {
529 return identifiers_
.size() != 0 &&
530 (identifiers_
[0] > 0 ||
531 identifiers_
[0] == POPUP_ITEM_ID_AUTOCOMPLETE_ENTRY
||
532 identifiers_
[0] == POPUP_ITEM_ID_PASSWORD_ENTRY
||
533 identifiers_
[0] == POPUP_ITEM_ID_DATALIST_ENTRY
);
536 void AutofillPopupControllerImpl::SetValues(
537 const std::vector
<base::string16
>& names
,
538 const std::vector
<base::string16
>& subtexts
,
539 const std::vector
<base::string16
>& icons
,
540 const std::vector
<int>& identifiers
) {
543 subtexts_
= subtexts
;
545 identifiers_
= identifiers
;
548 void AutofillPopupControllerImpl::ShowView() {
552 void AutofillPopupControllerImpl::InvalidateRow(size_t row
) {
554 DCHECK(row
< identifiers_
.size());
555 view_
->InvalidateRow(row
);
558 #if !defined(OS_ANDROID)
559 int AutofillPopupControllerImpl::GetDesiredPopupWidth() const {
560 int popup_width
= controller_common_
->RoundedElementBounds().width();
561 DCHECK_EQ(names().size(), subtexts().size());
562 for (size_t i
= 0; i
< names().size(); ++i
) {
564 gfx::GetStringWidth(names()[i
], name_font_list_
) +
565 gfx::GetStringWidth(subtexts()[i
], subtext_font_list_
) +
566 RowWidthWithoutText(i
);
568 popup_width
= std::max(popup_width
, row_size
);
574 int AutofillPopupControllerImpl::GetDesiredPopupHeight() const {
575 int popup_height
= 2 * kPopupBorderThickness
;
577 for (size_t i
= 0; i
< identifiers().size(); ++i
) {
578 popup_height
+= GetRowHeightFromId(identifiers()[i
]);
584 int AutofillPopupControllerImpl::RowWidthWithoutText(int row
) const {
585 int row_size
= kEndPadding
;
587 if (!subtexts_
[row
].empty())
588 row_size
+= kNamePadding
;
590 // Add the Autofill icon size, if required.
591 if (!icons_
[row
].empty()) {
592 int icon_width
= ui::ResourceBundle::GetSharedInstance().GetImageNamed(
593 GetIconResourceID(icons_
[row
])).Width();
594 row_size
+= icon_width
+ kIconPadding
;
597 // Add the padding at the end.
598 row_size
+= kEndPadding
;
600 // Add room for the popup border.
601 row_size
+= 2 * kPopupBorderThickness
;
606 void AutofillPopupControllerImpl::UpdatePopupBounds() {
607 int popup_width
= GetDesiredPopupWidth();
608 int popup_height
= GetDesiredPopupHeight();
610 popup_bounds_
= controller_common_
->GetPopupBounds(popup_width
,
613 #endif // !defined(OS_ANDROID)
615 WeakPtr
<AutofillPopupControllerImpl
> AutofillPopupControllerImpl::GetWeakPtr() {
616 return weak_ptr_factory_
.GetWeakPtr();
619 void AutofillPopupControllerImpl::ClearState() {
620 // Don't clear view_, because otherwise the popup will have to get regenerated
621 // and this will cause flickering.
623 popup_bounds_
= gfx::Rect();
628 identifiers_
.clear();
631 selected_line_
= kNoSelection
;
634 } // namespace autofill