Revert of Add button to add new FSP services to Files app. (patchset #8 id:140001...
[chromium-blink-merge.git] / chrome / browser / ui / autofill / autofill_popup_controller_impl.cc
blob2a65982912e8b1309ac3e452aaafbc464e570bbf
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 "components/autofill/core/browser/suggestion.h"
17 #include "content/public/browser/native_web_keyboard_event.h"
18 #include "grit/components_scaled_resources.h"
19 #include "ui/base/resource/resource_bundle.h"
20 #include "ui/events/event.h"
21 #include "ui/gfx/geometry/rect_conversions.h"
22 #include "ui/gfx/geometry/vector2d.h"
23 #include "ui/gfx/screen.h"
24 #include "ui/gfx/text_elider.h"
25 #include "ui/gfx/text_utils.h"
27 using base::WeakPtr;
29 namespace autofill {
30 namespace {
32 // Used to indicate that no line is currently selected by the user.
33 const int kNoSelection = -1;
35 // The vertical height of each row in pixels.
36 const size_t kRowHeight = 24;
38 // The vertical height of a separator in pixels.
39 const size_t kSeparatorHeight = 1;
41 #if !defined(OS_ANDROID)
42 // Size difference between name and label in pixels.
43 const int kLabelFontSizeDelta = -2;
45 const size_t kNamePadding = AutofillPopupView::kNamePadding;
46 const size_t kIconPadding = AutofillPopupView::kIconPadding;
47 const size_t kEndPadding = AutofillPopupView::kEndPadding;
48 #endif
50 struct DataResource {
51 const char* name;
52 int id;
55 const DataResource kDataResources[] = {
56 { "americanExpressCC", IDR_AUTOFILL_CC_AMEX },
57 { "dinersCC", IDR_AUTOFILL_CC_GENERIC },
58 { "discoverCC", IDR_AUTOFILL_CC_DISCOVER },
59 { "genericCC", IDR_AUTOFILL_CC_GENERIC },
60 { "jcbCC", IDR_AUTOFILL_CC_GENERIC },
61 { "masterCardCC", IDR_AUTOFILL_CC_MASTERCARD },
62 { "visaCC", IDR_AUTOFILL_CC_VISA },
63 { "scanCreditCardIcon", IDR_AUTOFILL_CC_SCAN_NEW },
64 #if defined(OS_MACOSX) && !defined(OS_IOS)
65 { "macContactsIcon", IDR_AUTOFILL_MAC_CONTACTS_ICON },
66 #endif // defined(OS_MACOSX) && !defined(OS_IOS)
69 } // namespace
71 // static
72 WeakPtr<AutofillPopupControllerImpl> AutofillPopupControllerImpl::GetOrCreate(
73 WeakPtr<AutofillPopupControllerImpl> previous,
74 WeakPtr<AutofillPopupDelegate> delegate,
75 content::WebContents* web_contents,
76 gfx::NativeView container_view,
77 const gfx::RectF& element_bounds,
78 base::i18n::TextDirection text_direction) {
79 if (previous.get() && previous->web_contents() == web_contents &&
80 previous->delegate_.get() == delegate.get() &&
81 previous->container_view() == container_view &&
82 previous->element_bounds() == element_bounds) {
83 previous->ClearState();
84 return previous;
87 if (previous.get())
88 previous->Hide();
90 AutofillPopupControllerImpl* controller =
91 new AutofillPopupControllerImpl(
92 delegate, web_contents, container_view, element_bounds,
93 text_direction);
94 return controller->GetWeakPtr();
97 AutofillPopupControllerImpl::AutofillPopupControllerImpl(
98 base::WeakPtr<AutofillPopupDelegate> delegate,
99 content::WebContents* web_contents,
100 gfx::NativeView container_view,
101 const gfx::RectF& element_bounds,
102 base::i18n::TextDirection text_direction)
103 : controller_common_(new PopupControllerCommon(element_bounds,
104 container_view,
105 web_contents)),
106 view_(NULL),
107 delegate_(delegate),
108 text_direction_(text_direction),
109 weak_ptr_factory_(this) {
110 ClearState();
111 controller_common_->SetKeyPressCallback(
112 base::Bind(&AutofillPopupControllerImpl::HandleKeyPressEvent,
113 base::Unretained(this)));
114 #if !defined(OS_ANDROID)
115 label_font_list_ = value_font_list_.DeriveWithSizeDelta(kLabelFontSizeDelta);
116 title_font_list_ = value_font_list_.DeriveWithStyle(gfx::Font::BOLD);
117 #if defined(OS_MACOSX)
118 // There is no italic version of the system font.
119 warning_font_list_ = value_font_list_;
120 #else
121 warning_font_list_ = value_font_list_.DeriveWithStyle(gfx::Font::ITALIC);
122 #endif
123 #endif
126 AutofillPopupControllerImpl::~AutofillPopupControllerImpl() {}
128 void AutofillPopupControllerImpl::Show(
129 const std::vector<autofill::Suggestion>& suggestions) {
130 SetValues(suggestions);
131 DCHECK_EQ(suggestions_.size(), elided_values_.size());
132 DCHECK_EQ(suggestions_.size(), elided_labels_.size());
134 #if !defined(OS_ANDROID)
135 // Android displays the long text with ellipsis using the view attributes.
137 UpdatePopupBounds();
138 int popup_width = popup_bounds().width();
140 // Elide the name and label strings so that the popup fits in the available
141 // space.
142 for (size_t i = 0; i < suggestions_.size(); ++i) {
143 int value_width =
144 gfx::GetStringWidth(suggestions_[i].value, GetValueFontListForRow(i));
145 int label_width =
146 gfx::GetStringWidth(suggestions_[i].label, GetLabelFontList());
147 int total_text_length = value_width + label_width;
149 // The line can have no strings if it represents a UI element, such as
150 // a separator line.
151 if (total_text_length == 0)
152 continue;
154 int available_width = popup_width - RowWidthWithoutText(i);
156 // Each field receives space in proportion to its length.
157 int value_size = available_width * value_width / total_text_length;
158 elided_values_[i] = gfx::ElideText(suggestions_[i].value,
159 GetValueFontListForRow(i),
160 value_size, gfx::ELIDE_TAIL);
162 int label_size = available_width * label_width / total_text_length;
163 elided_labels_[i] = gfx::ElideText(suggestions_[i].label,
164 GetLabelFontList(),
165 label_size, gfx::ELIDE_TAIL);
167 #endif
169 if (!view_) {
170 view_ = AutofillPopupView::Create(this);
172 // It is possible to fail to create the popup, in this case
173 // treat the popup as hiding right away.
174 if (!view_) {
175 Hide();
176 return;
179 ShowView();
180 } else {
181 UpdateBoundsAndRedrawPopup();
184 controller_common_->RegisterKeyPressCallback();
185 delegate_->OnPopupShown();
187 DCHECK_EQ(suggestions_.size(), elided_values_.size());
188 DCHECK_EQ(suggestions_.size(), elided_labels_.size());
191 void AutofillPopupControllerImpl::UpdateDataListValues(
192 const std::vector<base::string16>& values,
193 const std::vector<base::string16>& labels) {
194 DCHECK_EQ(suggestions_.size(), elided_values_.size());
195 DCHECK_EQ(suggestions_.size(), elided_labels_.size());
197 // Remove all the old data list values, which should always be at the top of
198 // the list if they are present.
199 while (!suggestions_.empty() &&
200 suggestions_[0].frontend_id == POPUP_ITEM_ID_DATALIST_ENTRY) {
201 suggestions_.erase(suggestions_.begin());
202 elided_values_.erase(elided_values_.begin());
203 elided_labels_.erase(elided_labels_.begin());
206 // If there are no new data list values, exit (clearing the separator if there
207 // is one).
208 if (values.empty()) {
209 if (!suggestions_.empty() &&
210 suggestions_[0].frontend_id == POPUP_ITEM_ID_SEPARATOR) {
211 suggestions_.erase(suggestions_.begin());
212 elided_values_.erase(elided_values_.begin());
213 elided_labels_.erase(elided_labels_.begin());
216 // The popup contents have changed, so either update the bounds or hide it.
217 if (HasSuggestions())
218 UpdateBoundsAndRedrawPopup();
219 else
220 Hide();
222 return;
225 // Add a separator if there are any other values.
226 if (!suggestions_.empty() &&
227 suggestions_[0].frontend_id != POPUP_ITEM_ID_SEPARATOR) {
228 suggestions_.insert(suggestions_.begin(), autofill::Suggestion());
229 suggestions_[0].frontend_id = POPUP_ITEM_ID_SEPARATOR;
230 elided_values_.insert(elided_values_.begin(), base::string16());
231 elided_labels_.insert(elided_labels_.begin(), base::string16());
234 // Prepend the parameters to the suggestions we already have.
235 suggestions_.insert(suggestions_.begin(), values.size(), Suggestion());
236 elided_values_.insert(elided_values_.begin(), values.size(),
237 base::string16());
238 elided_labels_.insert(elided_labels_.begin(), values.size(),
239 base::string16());
240 for (size_t i = 0; i < values.size(); i++) {
241 suggestions_[i].value = values[i];
242 suggestions_[i].label = labels[i];
243 suggestions_[i].frontend_id = POPUP_ITEM_ID_DATALIST_ENTRY;
245 // TODO(brettw) it looks like these should be elided.
246 elided_values_[i] = values[i];
247 elided_labels_[i] = labels[i];
250 UpdateBoundsAndRedrawPopup();
251 DCHECK_EQ(suggestions_.size(), elided_values_.size());
252 DCHECK_EQ(suggestions_.size(), elided_labels_.size());
255 void AutofillPopupControllerImpl::Hide() {
256 controller_common_->RemoveKeyPressCallback();
257 if (delegate_)
258 delegate_->OnPopupHidden();
260 if (view_)
261 view_->Hide();
263 delete this;
266 void AutofillPopupControllerImpl::ViewDestroyed() {
267 // The view has already been destroyed so clear the reference to it.
268 view_ = NULL;
270 Hide();
273 bool AutofillPopupControllerImpl::HandleKeyPressEvent(
274 const content::NativeWebKeyboardEvent& event) {
275 switch (event.windowsKeyCode) {
276 case ui::VKEY_UP:
277 SelectPreviousLine();
278 return true;
279 case ui::VKEY_DOWN:
280 SelectNextLine();
281 return true;
282 case ui::VKEY_PRIOR: // Page up.
283 // Set no line and then select the next line in case the first line is not
284 // selectable.
285 SetSelectedLine(kNoSelection);
286 SelectNextLine();
287 return true;
288 case ui::VKEY_NEXT: // Page down.
289 SetSelectedLine(GetLineCount() - 1);
290 return true;
291 case ui::VKEY_ESCAPE:
292 Hide();
293 return true;
294 case ui::VKEY_DELETE:
295 return (event.modifiers & content::NativeWebKeyboardEvent::ShiftKey) &&
296 RemoveSelectedLine();
297 case ui::VKEY_TAB:
298 // A tab press should cause the selected line to be accepted, but still
299 // return false so the tab key press propagates and changes the cursor
300 // location.
301 AcceptSelectedLine();
302 return false;
303 case ui::VKEY_RETURN:
304 return AcceptSelectedLine();
305 default:
306 return false;
310 void AutofillPopupControllerImpl::UpdateBoundsAndRedrawPopup() {
311 #if !defined(OS_ANDROID)
312 // TODO(csharp): Since UpdatePopupBounds can change the position of the popup,
313 // the popup could end up jumping from above the element to below it.
314 // It is unclear if it is better to keep the popup where it was, or if it
315 // should try and move to its desired position.
316 UpdatePopupBounds();
317 #endif
319 view_->UpdateBoundsAndRedrawPopup();
322 void AutofillPopupControllerImpl::SetSelectionAtPoint(const gfx::Point& point) {
323 SetSelectedLine(LineFromY(point.y()));
326 bool AutofillPopupControllerImpl::AcceptSelectedLine() {
327 if (selected_line_ == kNoSelection)
328 return false;
330 DCHECK_GE(selected_line_, 0);
331 DCHECK_LT(selected_line_, static_cast<int>(GetLineCount()));
333 if (!CanAccept(suggestions_[selected_line_].frontend_id))
334 return false;
336 AcceptSuggestion(selected_line_);
337 return true;
340 void AutofillPopupControllerImpl::SelectionCleared() {
341 SetSelectedLine(kNoSelection);
344 void AutofillPopupControllerImpl::AcceptSuggestion(size_t index) {
345 const autofill::Suggestion& suggestion = suggestions_[index];
346 delegate_->DidAcceptSuggestion(suggestion.value, suggestion.frontend_id);
349 int AutofillPopupControllerImpl::GetIconResourceID(
350 const base::string16& resource_name) const {
351 for (size_t i = 0; i < arraysize(kDataResources); ++i) {
352 if (resource_name == base::ASCIIToUTF16(kDataResources[i].name))
353 return kDataResources[i].id;
356 return -1;
359 bool AutofillPopupControllerImpl::CanDelete(size_t index) const {
360 // TODO(isherman): Native AddressBook suggestions on Mac and Android should
361 // not be considered to be deleteable.
362 int id = suggestions_[index].frontend_id;
363 return id > 0 || id == POPUP_ITEM_ID_AUTOCOMPLETE_ENTRY ||
364 id == POPUP_ITEM_ID_PASSWORD_ENTRY;
367 bool AutofillPopupControllerImpl::IsWarning(size_t index) const {
368 return suggestions_[index].frontend_id == POPUP_ITEM_ID_WARNING_MESSAGE;
371 gfx::Rect AutofillPopupControllerImpl::GetRowBounds(size_t index) {
372 int top = kPopupBorderThickness;
373 for (size_t i = 0; i < index; ++i) {
374 top += GetRowHeightFromId(suggestions_[i].frontend_id);
377 return gfx::Rect(
378 kPopupBorderThickness,
379 top,
380 popup_bounds_.width() - 2 * kPopupBorderThickness,
381 GetRowHeightFromId(suggestions_[index].frontend_id));
384 void AutofillPopupControllerImpl::SetPopupBounds(const gfx::Rect& bounds) {
385 popup_bounds_ = bounds;
386 UpdateBoundsAndRedrawPopup();
389 const gfx::Rect& AutofillPopupControllerImpl::popup_bounds() const {
390 return popup_bounds_;
393 content::WebContents* AutofillPopupControllerImpl::web_contents() {
394 return controller_common_->web_contents();
397 gfx::NativeView AutofillPopupControllerImpl::container_view() {
398 return controller_common_->container_view();
401 const gfx::RectF& AutofillPopupControllerImpl::element_bounds() const {
402 return controller_common_->element_bounds();
405 bool AutofillPopupControllerImpl::IsRTL() const {
406 return text_direction_ == base::i18n::RIGHT_TO_LEFT;
409 size_t AutofillPopupControllerImpl::GetLineCount() const {
410 return suggestions_.size();
413 const autofill::Suggestion& AutofillPopupControllerImpl::GetSuggestionAt(
414 size_t row) const {
415 return suggestions_[row];
418 const base::string16& AutofillPopupControllerImpl::GetElidedValueAt(
419 size_t row) const {
420 return elided_values_[row];
423 const base::string16& AutofillPopupControllerImpl::GetElidedLabelAt(
424 size_t row) const {
425 return elided_labels_[row];
428 #if !defined(OS_ANDROID)
429 const gfx::FontList& AutofillPopupControllerImpl::GetValueFontListForRow(
430 size_t index) const {
431 if (suggestions_[index].frontend_id == POPUP_ITEM_ID_WARNING_MESSAGE)
432 return warning_font_list_;
434 if (suggestions_[index].frontend_id == POPUP_ITEM_ID_TITLE)
435 return title_font_list_;
437 return value_font_list_;
440 const gfx::FontList& AutofillPopupControllerImpl::GetLabelFontList() const {
441 return label_font_list_;
443 #endif
445 int AutofillPopupControllerImpl::selected_line() const {
446 return selected_line_;
449 void AutofillPopupControllerImpl::SetSelectedLine(int selected_line) {
450 if (selected_line_ == selected_line)
451 return;
453 if (selected_line_ != kNoSelection &&
454 static_cast<size_t>(selected_line_) < suggestions_.size())
455 InvalidateRow(selected_line_);
457 if (selected_line != kNoSelection) {
458 InvalidateRow(selected_line);
460 if (!CanAccept(suggestions_[selected_line].frontend_id))
461 selected_line = kNoSelection;
464 selected_line_ = selected_line;
466 if (selected_line_ != kNoSelection) {
467 delegate_->DidSelectSuggestion(suggestions_[selected_line_].value,
468 suggestions_[selected_line_].frontend_id);
469 } else {
470 delegate_->ClearPreviewedForm();
474 void AutofillPopupControllerImpl::SelectNextLine() {
475 int new_selected_line = selected_line_ + 1;
477 // Skip over any lines that can't be selected.
478 while (static_cast<size_t>(new_selected_line) < GetLineCount() &&
479 !CanAccept(suggestions_[new_selected_line].frontend_id)) {
480 ++new_selected_line;
483 if (new_selected_line >= static_cast<int>(GetLineCount()))
484 new_selected_line = 0;
486 SetSelectedLine(new_selected_line);
489 void AutofillPopupControllerImpl::SelectPreviousLine() {
490 int new_selected_line = selected_line_ - 1;
492 // Skip over any lines that can't be selected.
493 while (new_selected_line > kNoSelection &&
494 !CanAccept(GetSuggestionAt(new_selected_line).frontend_id)) {
495 --new_selected_line;
498 if (new_selected_line <= kNoSelection)
499 new_selected_line = GetLineCount() - 1;
501 SetSelectedLine(new_selected_line);
504 bool AutofillPopupControllerImpl::RemoveSelectedLine() {
505 if (selected_line_ == kNoSelection)
506 return false;
508 DCHECK_GE(selected_line_, 0);
509 DCHECK_LT(selected_line_, static_cast<int>(GetLineCount()));
511 if (!CanDelete(selected_line_))
512 return false;
514 delegate_->RemoveSuggestion(suggestions_[selected_line_].value,
515 suggestions_[selected_line_].frontend_id);
517 // Remove the deleted element.
518 suggestions_.erase(suggestions_.begin() + selected_line_);
519 elided_values_.erase(elided_values_.begin() + selected_line_);
520 elided_labels_.erase(elided_labels_.begin() + selected_line_);
522 SetSelectedLine(kNoSelection);
524 if (HasSuggestions()) {
525 delegate_->ClearPreviewedForm();
526 UpdateBoundsAndRedrawPopup();
527 } else {
528 Hide();
531 return true;
534 int AutofillPopupControllerImpl::LineFromY(int y) {
535 int current_height = kPopupBorderThickness;
537 for (size_t i = 0; i < suggestions_.size(); ++i) {
538 current_height += GetRowHeightFromId(suggestions_[i].frontend_id);
540 if (y <= current_height)
541 return i;
544 // The y value goes beyond the popup so stop the selection at the last line.
545 return GetLineCount() - 1;
548 int AutofillPopupControllerImpl::GetRowHeightFromId(int identifier) const {
549 if (identifier == POPUP_ITEM_ID_SEPARATOR)
550 return kSeparatorHeight;
552 return kRowHeight;
555 bool AutofillPopupControllerImpl::CanAccept(int id) {
556 return id != POPUP_ITEM_ID_SEPARATOR && id != POPUP_ITEM_ID_WARNING_MESSAGE &&
557 id != POPUP_ITEM_ID_TITLE;
560 bool AutofillPopupControllerImpl::HasSuggestions() {
561 if (suggestions_.empty())
562 return false;
563 int id = suggestions_[0].frontend_id;
564 return id > 0 ||
565 id == POPUP_ITEM_ID_AUTOCOMPLETE_ENTRY ||
566 id == POPUP_ITEM_ID_PASSWORD_ENTRY ||
567 id == POPUP_ITEM_ID_DATALIST_ENTRY ||
568 id == POPUP_ITEM_ID_MAC_ACCESS_CONTACTS ||
569 id == POPUP_ITEM_ID_SCAN_CREDIT_CARD;
572 void AutofillPopupControllerImpl::SetValues(
573 const std::vector<autofill::Suggestion>& suggestions) {
574 suggestions_ = suggestions;
575 elided_values_.resize(suggestions.size());
576 elided_labels_.resize(suggestions.size());
577 for (size_t i = 0; i < suggestions.size(); i++) {
578 elided_values_[i] = suggestions[i].value;
579 elided_labels_[i] = suggestions[i].label;
583 void AutofillPopupControllerImpl::ShowView() {
584 view_->Show();
587 void AutofillPopupControllerImpl::InvalidateRow(size_t row) {
588 DCHECK(0 <= row);
589 DCHECK(row < suggestions_.size());
590 view_->InvalidateRow(row);
593 #if !defined(OS_ANDROID)
594 int AutofillPopupControllerImpl::GetDesiredPopupWidth() const {
595 int popup_width = controller_common_->RoundedElementBounds().width();
596 for (size_t i = 0; i < GetLineCount(); ++i) {
597 int row_size =
598 gfx::GetStringWidth(GetElidedValueAt(i), value_font_list_) +
599 gfx::GetStringWidth(GetElidedLabelAt(i), label_font_list_) +
600 RowWidthWithoutText(i);
602 popup_width = std::max(popup_width, row_size);
605 return popup_width;
608 int AutofillPopupControllerImpl::GetDesiredPopupHeight() const {
609 int popup_height = 2 * kPopupBorderThickness;
611 for (size_t i = 0; i < suggestions_.size(); ++i) {
612 popup_height += GetRowHeightFromId(suggestions_[i].frontend_id);
615 return popup_height;
618 int AutofillPopupControllerImpl::RowWidthWithoutText(int row) const {
619 int row_size = kEndPadding;
621 if (!elided_labels_[row].empty())
622 row_size += kNamePadding;
624 // Add the Autofill icon size, if required.
625 const base::string16& icon = suggestions_[row].icon;
626 if (!icon.empty()) {
627 int icon_width = ui::ResourceBundle::GetSharedInstance().GetImageNamed(
628 GetIconResourceID(icon)).Width();
629 row_size += icon_width + kIconPadding;
632 // Add the padding at the end.
633 row_size += kEndPadding;
635 // Add room for the popup border.
636 row_size += 2 * kPopupBorderThickness;
638 return row_size;
641 void AutofillPopupControllerImpl::UpdatePopupBounds() {
642 int popup_width = GetDesiredPopupWidth();
643 int popup_height = GetDesiredPopupHeight();
645 popup_bounds_ = controller_common_->GetPopupBounds(popup_width, popup_height);
647 #endif // !defined(OS_ANDROID)
649 WeakPtr<AutofillPopupControllerImpl> AutofillPopupControllerImpl::GetWeakPtr() {
650 return weak_ptr_factory_.GetWeakPtr();
653 void AutofillPopupControllerImpl::ClearState() {
654 // Don't clear view_, because otherwise the popup will have to get regenerated
655 // and this will cause flickering.
657 popup_bounds_ = gfx::Rect();
659 suggestions_.clear();
660 elided_values_.clear();
661 elided_labels_.clear();
663 selected_line_ = kNoSelection;
666 } // namespace autofill