autofill_popup_controller_impl.cc revision c5cede9ae108bb15f6b7a8aea21c7e1fefa2834c
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.
4
5#include "chrome/browser/ui/autofill/autofill_popup_controller_impl.h"
6
7#include <algorithm>
8#include <utility>
9
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"
25
26using base::WeakPtr;
27
28namespace autofill {
29namespace {
30
31// Used to indicate that no line is currently selected by the user.
32const int kNoSelection = -1;
33
34// The vertical height of each row in pixels.
35const size_t kRowHeight = 24;
36
37// The vertical height of a separator in pixels.
38const size_t kSeparatorHeight = 1;
39
40#if !defined(OS_ANDROID)
41// Size difference between name and subtext in pixels.
42const int kLabelFontSizeDelta = -2;
43
44const size_t kNamePadding = AutofillPopupView::kNamePadding;
45const size_t kIconPadding = AutofillPopupView::kIconPadding;
46const size_t kEndPadding = AutofillPopupView::kEndPadding;
47#endif
48
49struct DataResource {
50  const char* name;
51  int id;
52};
53
54const 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};
63
64}  // namespace
65
66// static
67WeakPtr<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());
75
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;
81  }
82
83  if (previous.get())
84    previous->Hide();
85
86  AutofillPopupControllerImpl* controller =
87      new AutofillPopupControllerImpl(
88          delegate, web_contents, container_view, element_bounds,
89          text_direction);
90  return controller->GetWeakPtr();
91}
92
93AutofillPopupControllerImpl::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      weak_ptr_factory_(this) {
106  ClearState();
107  controller_common_->SetKeyPressCallback(
108      base::Bind(&AutofillPopupControllerImpl::HandleKeyPressEvent,
109                 base::Unretained(this)));
110#if !defined(OS_ANDROID)
111  subtext_font_list_ = name_font_list_.DeriveWithSizeDelta(kLabelFontSizeDelta);
112#if defined(OS_MACOSX)
113  // There is no italic version of the system font.
114  warning_font_list_ = name_font_list_;
115#else
116  warning_font_list_ = name_font_list_.DeriveWithStyle(gfx::Font::ITALIC);
117#endif
118#endif
119}
120
121AutofillPopupControllerImpl::~AutofillPopupControllerImpl() {}
122
123void AutofillPopupControllerImpl::Show(
124    const std::vector<base::string16>& names,
125    const std::vector<base::string16>& subtexts,
126    const std::vector<base::string16>& icons,
127    const std::vector<int>& identifiers) {
128  SetValues(names, subtexts, icons, identifiers);
129
130#if !defined(OS_ANDROID)
131  // Android displays the long text with ellipsis using the view attributes.
132
133  UpdatePopupBounds();
134  int popup_width = popup_bounds().width();
135
136  // Elide the name and subtext strings so that the popup fits in the available
137  // space.
138  for (size_t i = 0; i < names_.size(); ++i) {
139    int name_width = gfx::GetStringWidth(names_[i], GetNameFontListForRow(i));
140    int subtext_width = gfx::GetStringWidth(subtexts_[i], subtext_font_list());
141    int total_text_length = name_width + subtext_width;
142
143    // The line can have no strings if it represents a UI element, such as
144    // a separator line.
145    if (total_text_length == 0)
146      continue;
147
148    int available_width = popup_width - RowWidthWithoutText(i);
149
150    // Each field recieves space in proportion to its length.
151    int name_size = available_width * name_width / total_text_length;
152    names_[i] = gfx::ElideText(names_[i],
153                               GetNameFontListForRow(i),
154                               name_size,
155                               gfx::ELIDE_AT_END);
156
157    int subtext_size = available_width * subtext_width / total_text_length;
158    subtexts_[i] = gfx::ElideText(subtexts_[i],
159                                  subtext_font_list(),
160                                  subtext_size,
161                                  gfx::ELIDE_AT_END);
162  }
163#endif
164
165  if (!view_) {
166    view_ = AutofillPopupView::Create(this);
167
168    // It is possible to fail to create the popup, in this case
169    // treat the popup as hiding right away.
170    if (!view_) {
171      Hide();
172      return;
173    }
174
175    ShowView();
176  } else {
177    UpdateBoundsAndRedrawPopup();
178  }
179
180  delegate_->OnPopupShown();
181  controller_common_->RegisterKeyPressCallback();
182}
183
184void AutofillPopupControllerImpl::UpdateDataListValues(
185    const std::vector<base::string16>& values,
186    const std::vector<base::string16>& labels) {
187  // Remove all the old data list values, which should always be at the top of
188  // the list if they are present.
189  while (!identifiers_.empty() &&
190         identifiers_[0] == POPUP_ITEM_ID_DATALIST_ENTRY) {
191    names_.erase(names_.begin());
192    subtexts_.erase(subtexts_.begin());
193    icons_.erase(icons_.begin());
194    identifiers_.erase(identifiers_.begin());
195  }
196
197  // If there are no new data list values, exit (clearing the separator if there
198  // is one).
199  if (values.empty()) {
200    if (!identifiers_.empty() && identifiers_[0] == POPUP_ITEM_ID_SEPARATOR) {
201      names_.erase(names_.begin());
202      subtexts_.erase(subtexts_.begin());
203      icons_.erase(icons_.begin());
204      identifiers_.erase(identifiers_.begin());
205    }
206
207     // The popup contents have changed, so either update the bounds or hide it.
208    if (HasSuggestions())
209      UpdateBoundsAndRedrawPopup();
210    else
211      Hide();
212
213    return;
214  }
215
216  // Add a separator if there are any other values.
217  if (!identifiers_.empty() && identifiers_[0] != POPUP_ITEM_ID_SEPARATOR) {
218    names_.insert(names_.begin(), base::string16());
219    subtexts_.insert(subtexts_.begin(), base::string16());
220    icons_.insert(icons_.begin(), base::string16());
221    identifiers_.insert(identifiers_.begin(), POPUP_ITEM_ID_SEPARATOR);
222  }
223
224
225  names_.insert(names_.begin(), values.begin(), values.end());
226  subtexts_.insert(subtexts_.begin(), labels.begin(), labels.end());
227
228  // Add the values that are the same for all data list elements.
229  icons_.insert(icons_.begin(), values.size(), base::string16());
230  identifiers_.insert(
231      identifiers_.begin(), values.size(), POPUP_ITEM_ID_DATALIST_ENTRY);
232
233  UpdateBoundsAndRedrawPopup();
234}
235
236void AutofillPopupControllerImpl::Hide() {
237  controller_common_->RemoveKeyPressCallback();
238  if (delegate_)
239    delegate_->OnPopupHidden();
240
241  if (view_)
242    view_->Hide();
243
244  delete this;
245}
246
247void AutofillPopupControllerImpl::ViewDestroyed() {
248  // The view has already been destroyed so clear the reference to it.
249  view_ = NULL;
250
251  Hide();
252}
253
254bool AutofillPopupControllerImpl::HandleKeyPressEvent(
255    const content::NativeWebKeyboardEvent& event) {
256  switch (event.windowsKeyCode) {
257    case ui::VKEY_UP:
258      SelectPreviousLine();
259      return true;
260    case ui::VKEY_DOWN:
261      SelectNextLine();
262      return true;
263    case ui::VKEY_PRIOR:  // Page up.
264      SetSelectedLine(0);
265      return true;
266    case ui::VKEY_NEXT:  // Page down.
267      SetSelectedLine(names().size() - 1);
268      return true;
269    case ui::VKEY_ESCAPE:
270      Hide();
271      return true;
272    case ui::VKEY_DELETE:
273      return (event.modifiers & content::NativeWebKeyboardEvent::ShiftKey) &&
274             RemoveSelectedLine();
275    case ui::VKEY_TAB:
276      // A tab press should cause the selected line to be accepted, but still
277      // return false so the tab key press propagates and changes the cursor
278      // location.
279      AcceptSelectedLine();
280      return false;
281    case ui::VKEY_RETURN:
282      return AcceptSelectedLine();
283    default:
284      return false;
285  }
286}
287
288void AutofillPopupControllerImpl::UpdateBoundsAndRedrawPopup() {
289#if !defined(OS_ANDROID)
290  // TODO(csharp): Since UpdatePopupBounds can change the position of the popup,
291  // the popup could end up jumping from above the element to below it.
292  // It is unclear if it is better to keep the popup where it was, or if it
293  // should try and move to its desired position.
294  UpdatePopupBounds();
295#endif
296
297  view_->UpdateBoundsAndRedrawPopup();
298}
299
300void AutofillPopupControllerImpl::SetSelectionAtPoint(const gfx::Point& point) {
301  SetSelectedLine(LineFromY(point.y()));
302}
303
304bool AutofillPopupControllerImpl::AcceptSelectedLine() {
305  if (selected_line_ == kNoSelection)
306    return false;
307
308  DCHECK_GE(selected_line_, 0);
309  DCHECK_LT(selected_line_, static_cast<int>(names_.size()));
310
311  if (!CanAccept(identifiers_[selected_line_]))
312    return false;
313
314  AcceptSuggestion(selected_line_);
315  return true;
316}
317
318void AutofillPopupControllerImpl::SelectionCleared() {
319  SetSelectedLine(kNoSelection);
320}
321
322void AutofillPopupControllerImpl::AcceptSuggestion(size_t index) {
323  delegate_->DidAcceptSuggestion(full_names_[index], identifiers_[index]);
324}
325
326int AutofillPopupControllerImpl::GetIconResourceID(
327    const base::string16& resource_name) const {
328  for (size_t i = 0; i < arraysize(kDataResources); ++i) {
329    if (resource_name == base::ASCIIToUTF16(kDataResources[i].name))
330      return kDataResources[i].id;
331  }
332
333  return -1;
334}
335
336bool AutofillPopupControllerImpl::CanDelete(size_t index) const {
337  // TODO(isherman): Native AddressBook suggestions on Mac and Android should
338  // not be considered to be deleteable.
339  int id = identifiers_[index];
340  return id > 0 || id == POPUP_ITEM_ID_AUTOCOMPLETE_ENTRY ||
341         id == POPUP_ITEM_ID_PASSWORD_ENTRY;
342}
343
344bool AutofillPopupControllerImpl::IsWarning(size_t index) const {
345  return identifiers_[index] == POPUP_ITEM_ID_WARNING_MESSAGE;
346}
347
348gfx::Rect AutofillPopupControllerImpl::GetRowBounds(size_t index) {
349  int top = kPopupBorderThickness;
350  for (size_t i = 0; i < index; ++i) {
351    top += GetRowHeightFromId(identifiers()[i]);
352  }
353
354  return gfx::Rect(
355      kPopupBorderThickness,
356      top,
357      popup_bounds_.width() - 2 * kPopupBorderThickness,
358      GetRowHeightFromId(identifiers()[index]));
359}
360
361void AutofillPopupControllerImpl::SetPopupBounds(const gfx::Rect& bounds) {
362  popup_bounds_ = bounds;
363  UpdateBoundsAndRedrawPopup();
364}
365
366const gfx::Rect& AutofillPopupControllerImpl::popup_bounds() const {
367  return popup_bounds_;
368}
369
370content::WebContents* AutofillPopupControllerImpl::web_contents() {
371  return controller_common_->web_contents();
372}
373
374gfx::NativeView AutofillPopupControllerImpl::container_view() {
375  return controller_common_->container_view();
376}
377
378const gfx::RectF& AutofillPopupControllerImpl::element_bounds() const {
379  return controller_common_->element_bounds();
380}
381
382bool AutofillPopupControllerImpl::IsRTL() const {
383  return text_direction_ == base::i18n::RIGHT_TO_LEFT;
384}
385
386const std::vector<base::string16>& AutofillPopupControllerImpl::names() const {
387  return names_;
388}
389
390const std::vector<base::string16>& AutofillPopupControllerImpl::subtexts()
391    const {
392  return subtexts_;
393}
394
395const std::vector<base::string16>& AutofillPopupControllerImpl::icons() const {
396  return icons_;
397}
398
399const std::vector<int>& AutofillPopupControllerImpl::identifiers() const {
400  return identifiers_;
401}
402
403#if !defined(OS_ANDROID)
404const gfx::FontList& AutofillPopupControllerImpl::GetNameFontListForRow(
405    size_t index) const {
406  if (identifiers_[index] == POPUP_ITEM_ID_WARNING_MESSAGE)
407    return warning_font_list_;
408
409  return name_font_list_;
410}
411
412const gfx::FontList& AutofillPopupControllerImpl::subtext_font_list() const {
413  return subtext_font_list_;
414}
415#endif
416
417int AutofillPopupControllerImpl::selected_line() const {
418  return selected_line_;
419}
420
421void AutofillPopupControllerImpl::SetSelectedLine(int selected_line) {
422  if (selected_line_ == selected_line)
423    return;
424
425  if (selected_line_ != kNoSelection &&
426      static_cast<size_t>(selected_line_) < identifiers_.size())
427    InvalidateRow(selected_line_);
428
429  if (selected_line != kNoSelection)
430    InvalidateRow(selected_line);
431
432  selected_line_ = selected_line;
433
434  if (selected_line_ != kNoSelection) {
435    delegate_->DidSelectSuggestion(names_[selected_line_],
436                                   identifiers_[selected_line_]);
437  } else {
438    delegate_->ClearPreviewedForm();
439  }
440}
441
442void AutofillPopupControllerImpl::SelectNextLine() {
443  int new_selected_line = selected_line_ + 1;
444
445  // Skip over any lines that can't be selected.
446  while (static_cast<size_t>(new_selected_line) < names_.size() &&
447         !CanAccept(identifiers()[new_selected_line])) {
448    ++new_selected_line;
449  }
450
451  if (new_selected_line >= static_cast<int>(names_.size()))
452    new_selected_line = 0;
453
454  SetSelectedLine(new_selected_line);
455}
456
457void AutofillPopupControllerImpl::SelectPreviousLine() {
458  int new_selected_line = selected_line_ - 1;
459
460  // Skip over any lines that can't be selected.
461  while (new_selected_line > kNoSelection &&
462         !CanAccept(identifiers()[new_selected_line])) {
463    --new_selected_line;
464  }
465
466  if (new_selected_line <= kNoSelection)
467    new_selected_line = names_.size() - 1;
468
469  SetSelectedLine(new_selected_line);
470}
471
472bool AutofillPopupControllerImpl::RemoveSelectedLine() {
473  if (selected_line_ == kNoSelection)
474    return false;
475
476  DCHECK_GE(selected_line_, 0);
477  DCHECK_LT(selected_line_, static_cast<int>(names_.size()));
478
479  if (!CanDelete(selected_line_))
480    return false;
481
482  delegate_->RemoveSuggestion(full_names_[selected_line_],
483                              identifiers_[selected_line_]);
484
485  // Remove the deleted element.
486  names_.erase(names_.begin() + selected_line_);
487  full_names_.erase(full_names_.begin() + selected_line_);
488  subtexts_.erase(subtexts_.begin() + selected_line_);
489  icons_.erase(icons_.begin() + selected_line_);
490  identifiers_.erase(identifiers_.begin() + selected_line_);
491
492  SetSelectedLine(kNoSelection);
493
494  if (HasSuggestions()) {
495    delegate_->ClearPreviewedForm();
496    UpdateBoundsAndRedrawPopup();
497  } else {
498    Hide();
499  }
500
501  return true;
502}
503
504int AutofillPopupControllerImpl::LineFromY(int y) {
505  int current_height = kPopupBorderThickness;
506
507  for (size_t i = 0; i < identifiers().size(); ++i) {
508    current_height += GetRowHeightFromId(identifiers()[i]);
509
510    if (y <= current_height)
511      return i;
512  }
513
514  // The y value goes beyond the popup so stop the selection at the last line.
515  return identifiers().size() - 1;
516}
517
518int AutofillPopupControllerImpl::GetRowHeightFromId(int identifier) const {
519  if (identifier == POPUP_ITEM_ID_SEPARATOR)
520    return kSeparatorHeight;
521
522  return kRowHeight;
523}
524
525bool AutofillPopupControllerImpl::CanAccept(int id) {
526  return id != POPUP_ITEM_ID_SEPARATOR && id != POPUP_ITEM_ID_WARNING_MESSAGE;
527}
528
529bool AutofillPopupControllerImpl::HasSuggestions() {
530  return identifiers_.size() != 0 &&
531         (identifiers_[0] > 0 ||
532          identifiers_[0] == POPUP_ITEM_ID_AUTOCOMPLETE_ENTRY ||
533          identifiers_[0] == POPUP_ITEM_ID_PASSWORD_ENTRY ||
534          identifiers_[0] == POPUP_ITEM_ID_DATALIST_ENTRY);
535}
536
537void AutofillPopupControllerImpl::SetValues(
538    const std::vector<base::string16>& names,
539    const std::vector<base::string16>& subtexts,
540    const std::vector<base::string16>& icons,
541    const std::vector<int>& identifiers) {
542  names_ = names;
543  full_names_ = names;
544  subtexts_ = subtexts;
545  icons_ = icons;
546  identifiers_ = identifiers;
547}
548
549void AutofillPopupControllerImpl::ShowView() {
550  view_->Show();
551}
552
553void AutofillPopupControllerImpl::InvalidateRow(size_t row) {
554  DCHECK(0 <= row);
555  DCHECK(row < identifiers_.size());
556  view_->InvalidateRow(row);
557}
558
559#if !defined(OS_ANDROID)
560int AutofillPopupControllerImpl::GetDesiredPopupWidth() const {
561  int popup_width = controller_common_->RoundedElementBounds().width();
562  DCHECK_EQ(names().size(), subtexts().size());
563  for (size_t i = 0; i < names().size(); ++i) {
564    int row_size =
565        gfx::GetStringWidth(names()[i], name_font_list_) +
566        gfx::GetStringWidth(subtexts()[i], subtext_font_list_) +
567        RowWidthWithoutText(i);
568
569    popup_width = std::max(popup_width, row_size);
570  }
571
572  return popup_width;
573}
574
575int AutofillPopupControllerImpl::GetDesiredPopupHeight() const {
576  int popup_height = 2 * kPopupBorderThickness;
577
578  for (size_t i = 0; i < identifiers().size(); ++i) {
579    popup_height += GetRowHeightFromId(identifiers()[i]);
580  }
581
582  return popup_height;
583}
584
585int AutofillPopupControllerImpl::RowWidthWithoutText(int row) const {
586  int row_size = kEndPadding;
587
588  if (!subtexts_[row].empty())
589    row_size += kNamePadding;
590
591  // Add the Autofill icon size, if required.
592  if (!icons_[row].empty()) {
593    int icon_width = ui::ResourceBundle::GetSharedInstance().GetImageNamed(
594        GetIconResourceID(icons_[row])).Width();
595    row_size += icon_width + kIconPadding;
596  }
597
598  // Add the padding at the end.
599  row_size += kEndPadding;
600
601  // Add room for the popup border.
602  row_size += 2 * kPopupBorderThickness;
603
604  return row_size;
605}
606
607void AutofillPopupControllerImpl::UpdatePopupBounds() {
608  int popup_required_width = GetDesiredPopupWidth();
609  int popup_height = GetDesiredPopupHeight();
610
611  popup_bounds_ = controller_common_->GetPopupBounds(popup_height,
612                                                     popup_required_width);
613}
614#endif  // !defined(OS_ANDROID)
615
616WeakPtr<AutofillPopupControllerImpl> AutofillPopupControllerImpl::GetWeakPtr() {
617  return weak_ptr_factory_.GetWeakPtr();
618}
619
620void AutofillPopupControllerImpl::ClearState() {
621  // Don't clear view_, because otherwise the popup will have to get regenerated
622  // and this will cause flickering.
623
624  popup_bounds_ = gfx::Rect();
625
626  names_.clear();
627  subtexts_.clear();
628  icons_.clear();
629  identifiers_.clear();
630  full_names_.clear();
631
632  selected_line_ = kNoSelection;
633}
634
635}  // namespace autofill
636