Elide text in the new Autofill UI
[chromium-blink-merge.git] / chrome / browser / ui / autofill / autofill_popup_controller_impl.cc
blob7e28e376c01d1f46981d08adedc6f8fedd9f4110
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"
7 #include <algorithm>
8 #include <utility>
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;
25 namespace {
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
40 // pixels.
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;
52 #endif
54 struct DataResource {
55 const char* name;
56 int id;
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 },
70 } // end namespace
72 // static
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);
80 if (previous &&
81 previous->container_view() == container_view &&
82 previous->element_bounds() == element_bounds) {
83 return previous;
86 if (previous)
87 previous->Hide();
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)
97 : view_(NULL),
98 delegate_(delegate),
99 container_view_(container_view),
100 element_bounds_(element_bounds),
101 selected_line_(kNoSelection),
102 delete_icon_hovered_(false),
103 is_hiding_(false),
104 inform_delegate_of_destruction_(true) {
105 #if !defined(OS_ANDROID)
106 subtext_font_ = name_font_.DeriveFont(kLabelFontSizeDelta);
107 #endif
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) {
120 names_ = names;
121 full_names_ = names;
122 subtexts_ = subtexts;
123 icons_ = icons;
124 identifiers_ = identifiers;
126 #if !defined(OS_ANDROID)
127 // Android displays the long text with ellipsis using the view attributes.
129 UpdatePopupBounds();
130 int popup_width = popup_bounds().width();
132 // Elide the name and subtext strings so that the popup fits in the available
133 // space.
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
140 // a separator line.
141 if (total_text_length == 0)
142 continue;
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],
149 name_font(),
150 name_size,
151 ui::ELIDE_AT_END);
153 int subtext_size = available_width * subtext_width / total_text_length;
154 subtexts_[i] = ui::ElideText(subtexts_[i],
155 subtext_font(),
156 subtext_size,
157 ui::ELIDE_AT_END);
159 #endif
161 if (!view_) {
162 view_ = AutofillPopupView::Create(this);
163 ShowView();
164 } else {
165 UpdateBoundsAndRedrawPopup();
169 void AutofillPopupControllerImpl::Hide() {
170 inform_delegate_of_destruction_ = false;
171 HideInternal();
174 bool AutofillPopupControllerImpl::HandleKeyPressEvent(
175 const content::NativeWebKeyboardEvent& event) {
176 switch (event.windowsKeyCode) {
177 case ui::VKEY_UP:
178 SelectPreviousLine();
179 return true;
180 case ui::VKEY_DOWN:
181 SelectNextLine();
182 return true;
183 case ui::VKEY_PRIOR: // Page up.
184 SetSelectedLine(0);
185 return true;
186 case ui::VKEY_NEXT: // Page down.
187 SetSelectedLine(names().size() - 1);
188 return true;
189 case ui::VKEY_ESCAPE:
190 HideInternal();
191 return true;
192 case ui::VKEY_DELETE:
193 return (event.modifiers & content::NativeWebKeyboardEvent::ShiftKey) &&
194 RemoveSelectedLine();
195 case ui::VKEY_RETURN:
196 return AcceptSelectedLine();
197 default:
198 return false;
202 void AutofillPopupControllerImpl::ViewDestroyed() {
203 delete this;
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.
212 UpdatePopupBounds();
213 #endif
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) {
229 MouseHovered(x, y);
231 if (delete_icon_hovered_)
232 RemoveSelectedLine();
233 else
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;
252 return -1;
255 bool AutofillPopupControllerImpl::CanDelete(size_t index) const {
256 // TODO(isherman): AddressBook suggestions on Mac should not be drawn as
257 // deleteable.
258 int id = identifiers_[index];
259 return id > 0 ||
260 id == WebAutofillClient::MenuItemIDAutocompleteEntry ||
261 id == WebAutofillClient::MenuItemIDPasswordEntry;
264 gfx::Rect AutofillPopupControllerImpl::GetRowBounds(size_t index) {
265 int top = 0;
266 for (size_t i = 0; i < index; ++i) {
267 top += GetRowHeightFromId(identifiers()[i]);
270 return gfx::Rect(
272 top,
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 {
295 return names_;
298 const std::vector<string16>& AutofillPopupControllerImpl::subtexts() const {
299 return subtexts_;
302 const std::vector<string16>& AutofillPopupControllerImpl::icons() const {
303 return icons_;
306 const std::vector<int>& AutofillPopupControllerImpl::identifiers() const {
307 return identifiers_;
310 #if !defined(OS_ANDROID)
311 const gfx::Font& AutofillPopupControllerImpl::name_font() const {
312 return name_font_;
315 const gfx::Font& AutofillPopupControllerImpl::subtext_font() const {
316 return subtext_font_;
318 #endif
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() {
329 if (is_hiding_)
330 return;
331 is_hiding_ = true;
333 SetSelectedLine(kNoSelection);
335 if (view_)
336 view_->Hide();
337 else
338 delete this;
341 void AutofillPopupControllerImpl::SetSelectedLine(int selected_line) {
342 if (selected_line_ == selected_line)
343 return;
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_]);
355 else
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])) {
365 ++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])) {
380 --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)
391 return false;
393 DCHECK_GE(selected_line_, 0);
394 DCHECK_LT(selected_line_, static_cast<int>(names_.size()));
396 if (!CanAccept(identifiers_[selected_line_]))
397 return false;
399 AcceptSuggestion(selected_line_);
400 return true;
403 bool AutofillPopupControllerImpl::RemoveSelectedLine() {
404 if (selected_line_ == kNoSelection)
405 return false;
407 DCHECK_GE(selected_line_, 0);
408 DCHECK_LT(selected_line_, static_cast<int>(names_.size()));
410 if (!CanDelete(selected_line_))
411 return false;
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();
428 } else {
429 HideInternal();
432 return true;
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)
442 return i;
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;
453 return kRowHeight;
456 bool AutofillPopupControllerImpl::DeleteIconIsUnder(int x, int y) {
457 #if defined(OS_ANDROID)
458 return false;
459 #else
460 if (!CanDelete(selected_line()))
461 return false;
463 int row_start_y = 0;
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),
471 kDeleteIconWidth,
472 kDeleteIconHeight);
474 return delete_icon_bounds.Contains(x, y);
475 #endif
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 ||
486 identifiers_[0] ==
487 WebAutofillClient::MenuItemIDAutocompleteEntry ||
488 identifiers_[0] == WebAutofillClient::MenuItemIDPasswordEntry ||
489 identifiers_[0] == WebAutofillClient::MenuItemIDDataListEntry);
492 void AutofillPopupControllerImpl::ShowView() {
493 view_->Show();
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
504 // aren't present.
505 return 0;
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);
518 return popup_width;
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]);
528 return popup_height;
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.
539 if (CanDelete(row))
540 row_size += kDeleteIconWidth + kIconPadding;
542 // Add the padding at the end
543 row_size += kEndPadding;
545 return row_size;
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
553 // popup go could).
554 gfx::Point top_left_corner_of_popup = element_bounds().origin() +
555 gfx::Vector2d(element_bounds().width() - popup_required_width,
556 -popup_height);
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(
585 point);
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);
616 else
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);
647 } else {
648 // The popup must appear above the field.
649 return std::make_pair(top_growth_end - popup_required_height,
650 popup_required_height);