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/utf_string_conversions.h"
12 #include "chrome/browser/ui/autofill/autofill_popup_delegate.h"
13 #include "chrome/browser/ui/autofill/autofill_popup_view.h"
14 #include "content/public/browser/native_web_keyboard_event.h"
15 #include "grit/webkit_resources.h"
16 #include "third_party/WebKit/Source/WebKit/chromium/public/WebAutofillClient.h"
17 #include "ui/base/events/event.h"
18 #include "ui/base/text/text_elider.h"
19 #include "ui/gfx/display.h"
20 #include "ui/gfx/screen.h"
21 #include "ui/gfx/vector2d.h"
23 using WebKit::WebAutofillClient
;
27 // Used to indicate that no line is currently selected by the user.
28 const int kNoSelection
= -1;
30 // Size difference between name and subtext in pixels.
31 const int kLabelFontSizeDelta
= -2;
33 // The vertical height of each row in pixels.
34 const size_t kRowHeight
= 24;
36 // The vertical height of a separator in pixels.
37 const size_t kSeparatorHeight
= 1;
39 // The amount of minimum padding between the Autofill name and subtext in
41 const size_t kNamePadding
= 15;
43 // The maximum amount of characters to display from either the name or subtext.
44 const size_t kMaxTextLength
= 15;
46 #if !defined(OS_ANDROID)
47 const size_t kIconPadding
= AutofillPopupView::kIconPadding
;
48 const size_t kEndPadding
= AutofillPopupView::kEndPadding
;
49 const size_t kDeleteIconHeight
= AutofillPopupView::kDeleteIconHeight
;
50 const size_t kDeleteIconWidth
= AutofillPopupView::kDeleteIconWidth
;
51 const size_t kAutofillIconWidth
= AutofillPopupView::kAutofillIconWidth
;
59 const DataResource kDataResources
[] = {
60 { "americanExpressCC", IDR_AUTOFILL_CC_AMEX
},
61 { "dinersCC", IDR_AUTOFILL_CC_DINERS
},
62 { "discoverCC", IDR_AUTOFILL_CC_DISCOVER
},
63 { "genericCC", IDR_AUTOFILL_CC_GENERIC
},
64 { "jcbCC", IDR_AUTOFILL_CC_JCB
},
65 { "masterCardCC", IDR_AUTOFILL_CC_MASTERCARD
},
66 { "soloCC", IDR_AUTOFILL_CC_SOLO
},
67 { "visaCC", IDR_AUTOFILL_CC_VISA
},
73 AutofillPopupControllerImpl
* AutofillPopupControllerImpl::GetOrCreate(
74 AutofillPopupControllerImpl
* previous
,
75 AutofillPopupDelegate
* delegate
,
76 gfx::NativeView container_view
,
77 const gfx::Rect
& element_bounds
) {
78 DCHECK(!previous
|| previous
->delegate_
== delegate
);
81 previous
->container_view() == container_view
&&
82 previous
->element_bounds() == element_bounds
) {
89 return new AutofillPopupControllerImpl(
90 delegate
, container_view
, element_bounds
);
93 AutofillPopupControllerImpl::AutofillPopupControllerImpl(
94 AutofillPopupDelegate
* delegate
,
95 gfx::NativeView container_view
,
96 const gfx::Rect
& element_bounds
)
99 container_view_(container_view
),
100 element_bounds_(element_bounds
),
101 selected_line_(kNoSelection
),
102 delete_icon_hovered_(false),
104 inform_delegate_of_destruction_(true) {
105 #if !defined(OS_ANDROID)
106 subtext_font_
= name_font_
.DeriveFont(kLabelFontSizeDelta
);
110 AutofillPopupControllerImpl::~AutofillPopupControllerImpl() {
111 if (inform_delegate_of_destruction_
)
112 delegate_
->ControllerDestroyed();
115 void AutofillPopupControllerImpl::Show(
116 const std::vector
<string16
>& names
,
117 const std::vector
<string16
>& subtexts
,
118 const std::vector
<string16
>& icons
,
119 const std::vector
<int>& identifiers
) {
122 subtexts_
= subtexts
;
124 identifiers_
= identifiers
;
126 #if !defined(OS_ANDROID)
127 // Android displays the long text with ellipsis using the view attributes.
130 int popup_width
= popup_bounds().width();
132 // Elide the name and subtext strings so that the popup fits in the available
134 for (size_t i
= 0; i
< names_
.size(); ++i
) {
135 int name_width
= name_font().GetStringWidth(names_
[i
]);
136 int subtext_width
= subtext_font().GetStringWidth(subtexts_
[i
]);
137 int total_text_length
= name_width
+ subtext_width
;
139 // The line can have no strings if it represents a UI element, such as
141 if (total_text_length
== 0)
144 int available_width
= popup_width
- RowWidthWithoutText(i
);
146 // Each field recieves space in proportion to its length.
147 int name_size
= available_width
* name_width
/ total_text_length
;
148 names_
[i
] = ui::ElideText(names_
[i
],
153 int subtext_size
= available_width
* subtext_width
/ total_text_length
;
154 subtexts_
[i
] = ui::ElideText(subtexts_
[i
],
162 view_
= AutofillPopupView::Create(this);
165 UpdateBoundsAndRedrawPopup();
169 void AutofillPopupControllerImpl::Hide() {
170 inform_delegate_of_destruction_
= false;
174 bool AutofillPopupControllerImpl::HandleKeyPressEvent(
175 const content::NativeWebKeyboardEvent
& event
) {
176 switch (event
.windowsKeyCode
) {
178 SelectPreviousLine();
183 case ui::VKEY_PRIOR
: // Page up.
186 case ui::VKEY_NEXT
: // Page down.
187 SetSelectedLine(names().size() - 1);
189 case ui::VKEY_ESCAPE
:
192 case ui::VKEY_DELETE
:
193 return (event
.modifiers
& content::NativeWebKeyboardEvent::ShiftKey
) &&
194 RemoveSelectedLine();
195 case ui::VKEY_RETURN
:
196 return AcceptSelectedLine();
202 void AutofillPopupControllerImpl::ViewDestroyed() {
206 void AutofillPopupControllerImpl::UpdateBoundsAndRedrawPopup() {
207 #if !defined(OS_ANDROID)
208 // TODO(csharp): Since UpdatePopupBounds can change the position of the popup,
209 // the popup could end up jumping from above the element to below it.
210 // It is unclear if it is better to keep the popup where it was, or if it
211 // should try and move to its desired position.
215 view_
->UpdateBoundsAndRedrawPopup();
218 void AutofillPopupControllerImpl::MouseHovered(int x
, int y
) {
219 SetSelectedLine(LineFromY(y
));
221 bool delete_icon_hovered
= DeleteIconIsUnder(x
, y
);
222 if (delete_icon_hovered
!= delete_icon_hovered_
) {
223 delete_icon_hovered_
= delete_icon_hovered
;
224 InvalidateRow(selected_line());
228 void AutofillPopupControllerImpl::MouseClicked(int x
, int y
) {
231 if (delete_icon_hovered_
)
232 RemoveSelectedLine();
234 AcceptSelectedLine();
237 void AutofillPopupControllerImpl::MouseExitedPopup() {
238 SetSelectedLine(kNoSelection
);
241 void AutofillPopupControllerImpl::AcceptSuggestion(size_t index
) {
242 delegate_
->DidAcceptSuggestion(full_names_
[index
], identifiers_
[index
]);
245 int AutofillPopupControllerImpl::GetIconResourceID(
246 const string16
& resource_name
) {
247 for (size_t i
= 0; i
< arraysize(kDataResources
); ++i
) {
248 if (resource_name
== ASCIIToUTF16(kDataResources
[i
].name
))
249 return kDataResources
[i
].id
;
255 bool AutofillPopupControllerImpl::CanDelete(size_t index
) const {
256 // TODO(isherman): AddressBook suggestions on Mac should not be drawn as
258 int id
= identifiers_
[index
];
260 id
== WebAutofillClient::MenuItemIDAutocompleteEntry
||
261 id
== WebAutofillClient::MenuItemIDPasswordEntry
;
264 gfx::Rect
AutofillPopupControllerImpl::GetRowBounds(size_t index
) {
266 for (size_t i
= 0; i
< index
; ++i
) {
267 top
+= GetRowHeightFromId(identifiers()[i
]);
273 popup_bounds_
.width(),
274 GetRowHeightFromId(identifiers()[index
]));
277 void AutofillPopupControllerImpl::SetPopupBounds(const gfx::Rect
& bounds
) {
278 popup_bounds_
= bounds
;
279 UpdateBoundsAndRedrawPopup();
282 const gfx::Rect
& AutofillPopupControllerImpl::popup_bounds() const {
283 return popup_bounds_
;
286 gfx::NativeView
AutofillPopupControllerImpl::container_view() const {
287 return container_view_
;
290 const gfx::Rect
& AutofillPopupControllerImpl::element_bounds() const {
291 return element_bounds_
;
294 const std::vector
<string16
>& AutofillPopupControllerImpl::names() const {
298 const std::vector
<string16
>& AutofillPopupControllerImpl::subtexts() const {
302 const std::vector
<string16
>& AutofillPopupControllerImpl::icons() const {
306 const std::vector
<int>& AutofillPopupControllerImpl::identifiers() const {
310 #if !defined(OS_ANDROID)
311 const gfx::Font
& AutofillPopupControllerImpl::name_font() const {
315 const gfx::Font
& AutofillPopupControllerImpl::subtext_font() const {
316 return subtext_font_
;
320 int AutofillPopupControllerImpl::selected_line() const {
321 return selected_line_
;
324 bool AutofillPopupControllerImpl::delete_icon_hovered() const {
325 return delete_icon_hovered_
;
328 void AutofillPopupControllerImpl::HideInternal() {
333 SetSelectedLine(kNoSelection
);
341 void AutofillPopupControllerImpl::SetSelectedLine(int selected_line
) {
342 if (selected_line_
== selected_line
)
345 if (selected_line_
!= kNoSelection
)
346 InvalidateRow(selected_line_
);
348 if (selected_line
!= kNoSelection
)
349 InvalidateRow(selected_line
);
351 selected_line_
= selected_line
;
353 if (selected_line_
!= kNoSelection
)
354 delegate_
->DidSelectSuggestion(identifiers_
[selected_line_
]);
356 delegate_
->ClearPreviewedForm();
359 void AutofillPopupControllerImpl::SelectNextLine() {
360 int new_selected_line
= selected_line_
+ 1;
362 // Skip over any lines that can't be selected.
363 while (static_cast<size_t>(new_selected_line
) < names_
.size() &&
364 !CanAccept(identifiers()[new_selected_line
])) {
368 if (new_selected_line
== static_cast<int>(names_
.size()))
369 new_selected_line
= 0;
371 SetSelectedLine(new_selected_line
);
374 void AutofillPopupControllerImpl::SelectPreviousLine() {
375 int new_selected_line
= selected_line_
- 1;
377 // Skip over any lines that can't be selected.
378 while (new_selected_line
> kNoSelection
&&
379 !CanAccept(identifiers()[new_selected_line
])) {
383 if (new_selected_line
<= kNoSelection
)
384 new_selected_line
= names_
.size() - 1;
386 SetSelectedLine(new_selected_line
);
389 bool AutofillPopupControllerImpl::AcceptSelectedLine() {
390 if (selected_line_
== kNoSelection
)
393 DCHECK_GE(selected_line_
, 0);
394 DCHECK_LT(selected_line_
, static_cast<int>(names_
.size()));
396 if (!CanAccept(identifiers_
[selected_line_
]))
399 AcceptSuggestion(selected_line_
);
403 bool AutofillPopupControllerImpl::RemoveSelectedLine() {
404 if (selected_line_
== kNoSelection
)
407 DCHECK_GE(selected_line_
, 0);
408 DCHECK_LT(selected_line_
, static_cast<int>(names_
.size()));
410 if (!CanDelete(selected_line_
))
413 delegate_
->RemoveSuggestion(full_names_
[selected_line_
],
414 identifiers_
[selected_line_
]);
416 // Remove the deleted element.
417 names_
.erase(names_
.begin() + selected_line_
);
418 full_names_
.erase(full_names_
.begin() + selected_line_
);
419 subtexts_
.erase(subtexts_
.begin() + selected_line_
);
420 icons_
.erase(icons_
.begin() + selected_line_
);
421 identifiers_
.erase(identifiers_
.begin() + selected_line_
);
423 SetSelectedLine(kNoSelection
);
425 if (HasSuggestions()) {
426 delegate_
->ClearPreviewedForm();
427 UpdateBoundsAndRedrawPopup();
435 int AutofillPopupControllerImpl::LineFromY(int y
) {
436 int current_height
= 0;
438 for (size_t i
= 0; i
< identifiers().size(); ++i
) {
439 current_height
+= GetRowHeightFromId(identifiers()[i
]);
441 if (y
<= current_height
)
445 // The y value goes beyond the popup so stop the selection at the last line.
446 return identifiers().size() - 1;
449 int AutofillPopupControllerImpl::GetRowHeightFromId(int identifier
) const {
450 if (identifier
== WebAutofillClient::MenuItemIDSeparator
)
451 return kSeparatorHeight
;
456 bool AutofillPopupControllerImpl::DeleteIconIsUnder(int x
, int y
) {
457 #if defined(OS_ANDROID)
460 if (!CanDelete(selected_line()))
464 for (int i
= 0; i
< selected_line(); ++i
) {
465 row_start_y
+= GetRowHeightFromId(identifiers()[i
]);
468 gfx::Rect delete_icon_bounds
= gfx::Rect(
469 popup_bounds().width() - kDeleteIconWidth
- kIconPadding
,
470 row_start_y
+ ((kRowHeight
- kDeleteIconHeight
) / 2),
474 return delete_icon_bounds
.Contains(x
, y
);
478 bool AutofillPopupControllerImpl::CanAccept(int id
) {
479 return id
!= WebAutofillClient::MenuItemIDSeparator
&&
480 id
!= WebAutofillClient::MenuItemIDWarningMessage
;
483 bool AutofillPopupControllerImpl::HasSuggestions() {
484 return identifiers_
.size() != 0 &&
485 (identifiers_
[0] > 0 ||
487 WebAutofillClient::MenuItemIDAutocompleteEntry
||
488 identifiers_
[0] == WebAutofillClient::MenuItemIDPasswordEntry
||
489 identifiers_
[0] == WebAutofillClient::MenuItemIDDataListEntry
);
492 void AutofillPopupControllerImpl::ShowView() {
496 void AutofillPopupControllerImpl::InvalidateRow(size_t row
) {
497 view_
->InvalidateRow(row
);
500 #if !defined(OS_ANDROID)
501 int AutofillPopupControllerImpl::GetDesiredPopupWidth() const {
502 if (!name_font_
.platform_font() || !subtext_font_
.platform_font()) {
503 // We can't calculate the size of the popup if the fonts
508 int popup_width
= element_bounds().width();
509 DCHECK_EQ(names().size(), subtexts().size());
510 for (size_t i
= 0; i
< names().size(); ++i
) {
511 int row_size
= name_font_
.GetStringWidth(names()[i
]) +
512 subtext_font_
.GetStringWidth(subtexts()[i
]) +
513 RowWidthWithoutText(i
);
515 popup_width
= std::max(popup_width
, row_size
);
521 int AutofillPopupControllerImpl::GetDesiredPopupHeight() const {
522 int popup_height
= 0;
524 for (size_t i
= 0; i
< identifiers().size(); ++i
) {
525 popup_height
+= GetRowHeightFromId(identifiers()[i
]);
531 int AutofillPopupControllerImpl::RowWidthWithoutText(int row
) const {
532 int row_size
= kEndPadding
+ kNamePadding
;
534 // Add the Autofill icon size, if required.
535 if (!icons_
[row
].empty())
536 row_size
+= kAutofillIconWidth
+ kIconPadding
;
538 // Add the delete icon size, if required.
540 row_size
+= kDeleteIconWidth
+ kIconPadding
;
542 // Add the padding at the end
543 row_size
+= kEndPadding
;
548 void AutofillPopupControllerImpl::UpdatePopupBounds() {
549 int popup_required_width
= GetDesiredPopupWidth();
550 int popup_height
= GetDesiredPopupHeight();
551 // This is the top left point of the popup if the popup is above the element
552 // and grows to the left (since that is the highest and furthest left the
554 gfx::Point top_left_corner_of_popup
= element_bounds().origin() +
555 gfx::Vector2d(element_bounds().width() - popup_required_width
,
558 // This is the bottom right point of the popup if the popup is below the
559 // element and grows to the right (since the is the lowest and furthest right
560 // the popup could go).
561 gfx::Point bottom_right_corner_of_popup
= element_bounds().origin() +
562 gfx::Vector2d(popup_required_width
,
563 element_bounds().height() + popup_height
);
565 gfx::Display top_left_display
= GetDisplayNearestPoint(
566 top_left_corner_of_popup
);
567 gfx::Display bottom_right_display
= GetDisplayNearestPoint(
568 bottom_right_corner_of_popup
);
570 std::pair
<int, int> popup_x_and_width
= CalculatePopupXAndWidth(
571 top_left_display
, bottom_right_display
, popup_required_width
);
572 std::pair
<int, int> popup_y_and_height
= CalculatePopupYAndHeight(
573 top_left_display
, bottom_right_display
, popup_height
);
575 popup_bounds_
= gfx::Rect(popup_x_and_width
.first
,
576 popup_y_and_height
.first
,
577 popup_x_and_width
.second
,
578 popup_y_and_height
.second
);
580 #endif // !defined(OS_ANDROID)
582 gfx::Display
AutofillPopupControllerImpl::GetDisplayNearestPoint(
583 const gfx::Point
& point
) const {
584 return gfx::Screen::GetScreenFor(container_view())->GetDisplayNearestPoint(
588 std::pair
<int, int> AutofillPopupControllerImpl::CalculatePopupXAndWidth(
589 const gfx::Display
& left_display
,
590 const gfx::Display
& right_display
,
591 int popup_required_width
) const {
592 int leftmost_display_x
= left_display
.bounds().x() *
593 left_display
.device_scale_factor();
594 int rightmost_display_x
= right_display
.GetSizeInPixel().width() +
595 right_display
.bounds().x() * right_display
.device_scale_factor();
597 // Calculate the start coordinates for the popup if it is growing right or
598 // the end position if it is growing to the left, capped to screen space.
599 int right_growth_start
= std::max(leftmost_display_x
,
600 std::min(rightmost_display_x
,
601 element_bounds().x()));
602 int left_growth_end
= std::max(leftmost_display_x
,
603 std::min(rightmost_display_x
,
604 element_bounds().right()));
606 int right_available
= rightmost_display_x
- right_growth_start
;
607 int left_available
= left_growth_end
- leftmost_display_x
;
609 int popup_width
= std::min(popup_required_width
,
610 std::max(right_available
, left_available
));
612 // If there is enough space for the popup on the right, show it there,
613 // otherwise choose the larger size.
614 if (right_available
>= popup_width
|| right_available
>= left_available
)
615 return std::make_pair(right_growth_start
, popup_width
);
617 return std::make_pair(left_growth_end
- popup_width
, popup_width
);
620 std::pair
<int,int> AutofillPopupControllerImpl::CalculatePopupYAndHeight(
621 const gfx::Display
& top_display
,
622 const gfx::Display
& bottom_display
,
623 int popup_required_height
) const {
624 int topmost_display_y
= top_display
.bounds().y() *
625 top_display
.device_scale_factor();
626 int bottommost_display_y
= bottom_display
.GetSizeInPixel().height() +
627 (bottom_display
.bounds().y() *
628 bottom_display
.device_scale_factor());
630 // Calculate the start coordinates for the popup if it is growing down or
631 // the end position if it is growing up, capped to screen space.
632 int top_growth_end
= std::max(topmost_display_y
,
633 std::min(bottommost_display_y
,
634 element_bounds().y()));
635 int bottom_growth_start
= std::max(topmost_display_y
,
636 std::min(bottommost_display_y
,
637 element_bounds().bottom()));
639 int top_available
= bottom_growth_start
- topmost_display_y
;
640 int bottom_available
= bottommost_display_y
- top_growth_end
;
642 // TODO(csharp): Restrict the popup height to what is available.
643 if (bottom_available
>= popup_required_height
||
644 bottom_available
>= top_available
) {
645 // The popup can appear below the field.
646 return std::make_pair(bottom_growth_start
, popup_required_height
);
648 // The popup must appear above the field.
649 return std::make_pair(top_growth_end
- popup_required_height
,
650 popup_required_height
);