Add unit test for the Settings API Bubble.
[chromium-blink-merge.git] / chrome / browser / ui / autofill / autofill_popup_controller_impl.cc
blob4e6c6a03a4af5092c819f571acacfa8ac973940b
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/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"
26 using base::WeakPtr;
28 namespace autofill {
29 namespace {
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;
47 #endif
49 struct DataResource {
50 const char* name;
51 int id;
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 },
64 } // namespace
66 // static
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();
80 return previous;
83 if (previous.get())
84 previous->Hide();
86 AutofillPopupControllerImpl* controller =
87 new AutofillPopupControllerImpl(
88 delegate, web_contents, container_view, element_bounds,
89 text_direction);
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,
100 container_view,
101 web_contents)),
102 view_(NULL),
103 delegate_(delegate),
104 text_direction_(text_direction),
105 hide_on_outside_click_(false),
106 weak_ptr_factory_(this) {
107 ClearState();
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_;
116 #else
117 warning_font_list_ = name_font_list_.DeriveWithStyle(gfx::Font::ITALIC);
118 #endif
119 #endif
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.
134 UpdatePopupBounds();
135 int popup_width = popup_bounds().width();
137 // Elide the name and subtext strings so that the popup fits in the available
138 // space.
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
145 // a separator line.
146 if (total_text_length == 0)
147 continue;
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),
155 name_size,
156 gfx::ELIDE_AT_END);
158 int subtext_size = available_width * subtext_width / total_text_length;
159 subtexts_[i] = gfx::ElideText(subtexts_[i],
160 subtext_font_list(),
161 subtext_size,
162 gfx::ELIDE_AT_END);
164 #endif
166 if (!view_) {
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.
171 if (!view_) {
172 Hide();
173 return;
176 ShowView();
177 } else {
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
199 // is one).
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();
211 else
212 Hide();
214 return;
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());
231 identifiers_.insert(
232 identifiers_.begin(), values.size(), POPUP_ITEM_ID_DATALIST_ENTRY);
234 UpdateBoundsAndRedrawPopup();
237 void AutofillPopupControllerImpl::Hide() {
238 controller_common_->RemoveKeyPressCallback();
239 if (delegate_.get())
240 delegate_->OnPopupHidden();
242 if (view_)
243 view_->Hide();
245 delete this;
248 void AutofillPopupControllerImpl::ViewDestroyed() {
249 // The view has already been destroyed so clear the reference to it.
250 view_ = NULL;
252 Hide();
255 bool AutofillPopupControllerImpl::HandleKeyPressEvent(
256 const content::NativeWebKeyboardEvent& event) {
257 switch (event.windowsKeyCode) {
258 case ui::VKEY_UP:
259 SelectPreviousLine();
260 return true;
261 case ui::VKEY_DOWN:
262 SelectNextLine();
263 return true;
264 case ui::VKEY_PRIOR: // Page up.
265 SetSelectedLine(0);
266 return true;
267 case ui::VKEY_NEXT: // Page down.
268 SetSelectedLine(names().size() - 1);
269 return true;
270 case ui::VKEY_ESCAPE:
271 Hide();
272 return true;
273 case ui::VKEY_DELETE:
274 return (event.modifiers & content::NativeWebKeyboardEvent::ShiftKey) &&
275 RemoveSelectedLine();
276 case ui::VKEY_TAB:
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
279 // location.
280 AcceptSelectedLine();
281 return false;
282 case ui::VKEY_RETURN:
283 return AcceptSelectedLine();
284 default:
285 return false;
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.
295 UpdatePopupBounds();
296 #endif
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)
307 return false;
309 DCHECK_GE(selected_line_, 0);
310 DCHECK_LT(selected_line_, static_cast<int>(names_.size()));
312 if (!CanAccept(identifiers_[selected_line_]))
313 return false;
315 AcceptSuggestion(selected_line_);
316 return true;
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;
343 return -1;
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]);
364 return gfx::Rect(
365 kPopupBorderThickness,
366 top,
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 {
397 return names_;
400 const std::vector<base::string16>& AutofillPopupControllerImpl::subtexts()
401 const {
402 return subtexts_;
405 const std::vector<base::string16>& AutofillPopupControllerImpl::icons() const {
406 return icons_;
409 const std::vector<int>& AutofillPopupControllerImpl::identifiers() const {
410 return identifiers_;
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_;
425 #endif
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)
438 return;
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_]);
452 } else {
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])) {
463 ++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])) {
478 --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)
489 return false;
491 DCHECK_GE(selected_line_, 0);
492 DCHECK_LT(selected_line_, static_cast<int>(names_.size()));
494 if (!CanDelete(selected_line_))
495 return false;
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();
512 } else {
513 Hide();
516 return true;
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)
526 return i;
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;
537 return kRowHeight;
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) {
557 names_ = names;
558 full_names_ = names;
559 subtexts_ = subtexts;
560 icons_ = icons;
561 identifiers_ = identifiers;
564 void AutofillPopupControllerImpl::ShowView() {
565 view_->Show();
568 void AutofillPopupControllerImpl::InvalidateRow(size_t row) {
569 DCHECK(0 <= 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) {
579 int row_size =
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);
587 return popup_width;
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]);
597 return popup_height;
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;
619 return row_size;
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();
641 names_.clear();
642 subtexts_.clear();
643 icons_.clear();
644 identifiers_.clear();
645 full_names_.clear();
647 selected_line_ = kNoSelection;
650 } // namespace autofill