Extend EnrollmentHandler to handle consumer management.
[chromium-blink-merge.git] / chrome / browser / ui / autofill / autofill_popup_controller_impl.cc
blobae0a5f29771f936724431102b85990b3476c863d
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 },
62 #if defined(OS_MACOSX) && !defined(OS_IOS)
63 { "macContactsIcon", IDR_AUTOFILL_MAC_CONTACTS_ICON },
64 #endif // defined(OS_MACOSX) && !defined(OS_IOS)
67 } // namespace
69 // static
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();
83 return previous;
86 if (previous.get())
87 previous->Hide();
89 AutofillPopupControllerImpl* controller =
90 new AutofillPopupControllerImpl(
91 delegate, web_contents, container_view, element_bounds,
92 text_direction);
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,
103 container_view,
104 web_contents)),
105 view_(NULL),
106 delegate_(delegate),
107 text_direction_(text_direction),
108 weak_ptr_factory_(this) {
109 ClearState();
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_;
118 #else
119 warning_font_list_ = name_font_list_.DeriveWithStyle(gfx::Font::ITALIC);
120 #endif
121 #endif
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.
136 UpdatePopupBounds();
137 int popup_width = popup_bounds().width();
139 // Elide the name and subtext strings so that the popup fits in the available
140 // space.
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
147 // a separator line.
148 if (total_text_length == 0)
149 continue;
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);
162 #endif
164 if (!view_) {
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.
169 if (!view_) {
170 Hide();
171 return;
174 ShowView();
175 } else {
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
197 // is one).
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();
209 else
210 Hide();
212 return;
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());
229 identifiers_.insert(
230 identifiers_.begin(), values.size(), POPUP_ITEM_ID_DATALIST_ENTRY);
232 UpdateBoundsAndRedrawPopup();
235 void AutofillPopupControllerImpl::Hide() {
236 controller_common_->RemoveKeyPressCallback();
237 if (delegate_)
238 delegate_->OnPopupHidden();
240 if (view_)
241 view_->Hide();
243 delete this;
246 void AutofillPopupControllerImpl::ViewDestroyed() {
247 // The view has already been destroyed so clear the reference to it.
248 view_ = NULL;
250 Hide();
253 bool AutofillPopupControllerImpl::HandleKeyPressEvent(
254 const content::NativeWebKeyboardEvent& event) {
255 switch (event.windowsKeyCode) {
256 case ui::VKEY_UP:
257 SelectPreviousLine();
258 return true;
259 case ui::VKEY_DOWN:
260 SelectNextLine();
261 return true;
262 case ui::VKEY_PRIOR: // Page up.
263 SetSelectedLine(0);
264 return true;
265 case ui::VKEY_NEXT: // Page down.
266 SetSelectedLine(names().size() - 1);
267 return true;
268 case ui::VKEY_ESCAPE:
269 Hide();
270 return true;
271 case ui::VKEY_DELETE:
272 return (event.modifiers & content::NativeWebKeyboardEvent::ShiftKey) &&
273 RemoveSelectedLine();
274 case ui::VKEY_TAB:
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
277 // location.
278 AcceptSelectedLine();
279 return false;
280 case ui::VKEY_RETURN:
281 return AcceptSelectedLine();
282 default:
283 return false;
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.
293 UpdatePopupBounds();
294 #endif
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)
305 return false;
307 DCHECK_GE(selected_line_, 0);
308 DCHECK_LT(selected_line_, static_cast<int>(names_.size()));
310 if (!CanAccept(identifiers_[selected_line_]))
311 return false;
313 AcceptSuggestion(selected_line_);
314 return true;
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;
332 return -1;
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]);
353 return gfx::Rect(
354 kPopupBorderThickness,
355 top,
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 {
386 return names_;
389 const std::vector<base::string16>& AutofillPopupControllerImpl::subtexts()
390 const {
391 return subtexts_;
394 const std::vector<base::string16>& AutofillPopupControllerImpl::icons() const {
395 return icons_;
398 const std::vector<int>& AutofillPopupControllerImpl::identifiers() const {
399 return identifiers_;
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_;
414 #endif
416 int AutofillPopupControllerImpl::selected_line() const {
417 return selected_line_;
420 void AutofillPopupControllerImpl::SetSelectedLine(int selected_line) {
421 if (selected_line_ == selected_line)
422 return;
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_]);
436 } else {
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])) {
447 ++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])) {
462 --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)
473 return false;
475 DCHECK_GE(selected_line_, 0);
476 DCHECK_LT(selected_line_, static_cast<int>(names_.size()));
478 if (!CanDelete(selected_line_))
479 return false;
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();
496 } else {
497 Hide();
500 return true;
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)
510 return i;
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;
521 return kRowHeight;
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) {
541 names_ = names;
542 full_names_ = names;
543 subtexts_ = subtexts;
544 icons_ = icons;
545 identifiers_ = identifiers;
548 void AutofillPopupControllerImpl::ShowView() {
549 view_->Show();
552 void AutofillPopupControllerImpl::InvalidateRow(size_t row) {
553 DCHECK(0 <= 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) {
563 int row_size =
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);
571 return popup_width;
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]);
581 return popup_height;
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;
603 return row_size;
606 void AutofillPopupControllerImpl::UpdatePopupBounds() {
607 int popup_width = GetDesiredPopupWidth();
608 int popup_height = GetDesiredPopupHeight();
610 popup_bounds_ = controller_common_->GetPopupBounds(popup_width,
611 popup_height);
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();
625 names_.clear();
626 subtexts_.clear();
627 icons_.clear();
628 identifiers_.clear();
629 full_names_.clear();
631 selected_line_ = kNoSelection;
634 } // namespace autofill