autofill_popup_controller_impl.cc revision 868fa2fe829687343ffae624259930155e16dbd8
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 "components/autofill/browser/autofill_popup_delegate.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/rect_conversions.h"
21#include "ui/gfx/screen.h"
22#include "ui/gfx/vector2d.h"
23
24using base::WeakPtr;
25using WebKit::WebAutofillClient;
26
27namespace autofill {
28namespace {
29
30// Used to indicate that no line is currently selected by the user.
31const int kNoSelection = -1;
32
33// Size difference between name and subtext in pixels.
34const int kLabelFontSizeDelta = -2;
35
36// The vertical height of each row in pixels.
37const size_t kRowHeight = 24;
38
39// The vertical height of a separator in pixels.
40const size_t kSeparatorHeight = 1;
41
42// The maximum amount of characters to display from either the name or subtext.
43const size_t kMaxTextLength = 15;
44
45#if !defined(OS_ANDROID)
46const size_t kNamePadding = AutofillPopupView::kNamePadding;
47const size_t kIconPadding = AutofillPopupView::kIconPadding;
48const size_t kEndPadding = AutofillPopupView::kEndPadding;
49const size_t kAutofillIconWidth = AutofillPopupView::kAutofillIconWidth;
50#endif
51
52struct DataResource {
53  const char* name;
54  int id;
55};
56
57const DataResource kDataResources[] = {
58  { "americanExpressCC", IDR_AUTOFILL_CC_AMEX },
59  { "dinersCC", IDR_AUTOFILL_CC_DINERS },
60  { "discoverCC", IDR_AUTOFILL_CC_DISCOVER },
61  { "genericCC", IDR_AUTOFILL_CC_GENERIC },
62  { "jcbCC", IDR_AUTOFILL_CC_JCB },
63  { "masterCardCC", IDR_AUTOFILL_CC_MASTERCARD },
64  { "soloCC", IDR_AUTOFILL_CC_SOLO },
65  { "visaCC", IDR_AUTOFILL_CC_VISA },
66};
67
68}  // namespace
69
70// static
71WeakPtr<AutofillPopupControllerImpl> AutofillPopupControllerImpl::GetOrCreate(
72    WeakPtr<AutofillPopupControllerImpl> previous,
73    WeakPtr<AutofillPopupDelegate> delegate,
74    gfx::NativeView container_view,
75    const gfx::RectF& element_bounds) {
76  DCHECK(!previous.get() || previous->delegate_.get() == delegate.get());
77
78  if (previous.get() && previous->container_view() == container_view &&
79      previous->element_bounds() == element_bounds) {
80    previous->ClearState();
81    return previous;
82  }
83
84  if (previous.get())
85    previous->Hide();
86
87  AutofillPopupControllerImpl* controller =
88      new AutofillPopupControllerImpl(delegate, container_view, element_bounds);
89  return controller->GetWeakPtr();
90}
91
92AutofillPopupControllerImpl::AutofillPopupControllerImpl(
93    base::WeakPtr<AutofillPopupDelegate> delegate,
94    gfx::NativeView container_view,
95    const gfx::RectF& element_bounds)
96    : view_(NULL),
97      delegate_(delegate),
98      container_view_(container_view),
99      element_bounds_(element_bounds),
100      weak_ptr_factory_(this) {
101  ClearState();
102#if !defined(OS_ANDROID)
103  subtext_font_ = name_font_.DeriveFont(kLabelFontSizeDelta);
104  warning_font_ = name_font_.DeriveFont(0, gfx::Font::ITALIC);
105#endif
106}
107
108AutofillPopupControllerImpl::~AutofillPopupControllerImpl() {}
109
110void AutofillPopupControllerImpl::Show(
111    const std::vector<string16>& names,
112    const std::vector<string16>& subtexts,
113    const std::vector<string16>& icons,
114    const std::vector<int>& identifiers) {
115  SetValues(names, subtexts, icons, identifiers);
116
117#if !defined(OS_ANDROID)
118  // Android displays the long text with ellipsis using the view attributes.
119
120  UpdatePopupBounds();
121  int popup_width = popup_bounds().width();
122
123  // Elide the name and subtext strings so that the popup fits in the available
124  // space.
125  for (size_t i = 0; i < names_.size(); ++i) {
126    int name_width = GetNameFontForRow(i).GetStringWidth(names_[i]);
127    int subtext_width = subtext_font().GetStringWidth(subtexts_[i]);
128    int total_text_length = name_width + subtext_width;
129
130    // The line can have no strings if it represents a UI element, such as
131    // a separator line.
132    if (total_text_length == 0)
133      continue;
134
135    int available_width = popup_width - RowWidthWithoutText(i);
136
137    // Each field recieves space in proportion to its length.
138    int name_size = available_width * name_width / total_text_length;
139    names_[i] = ui::ElideText(names_[i],
140                              GetNameFontForRow(i),
141                              name_size,
142                              ui::ELIDE_AT_END);
143
144    int subtext_size = available_width * subtext_width / total_text_length;
145    subtexts_[i] = ui::ElideText(subtexts_[i],
146                                 subtext_font(),
147                                 subtext_size,
148                                 ui::ELIDE_AT_END);
149  }
150#endif
151
152  if (!view_) {
153    view_ = AutofillPopupView::Create(this);
154
155    // It is possible to fail to create the popup, in this case
156    // treat the popup as hiding right away.
157    if (!view_) {
158      Hide();
159      return;
160    }
161
162    ShowView();
163  } else {
164    UpdateBoundsAndRedrawPopup();
165  }
166
167  delegate_->OnPopupShown(this);
168}
169
170void AutofillPopupControllerImpl::Hide() {
171  if (delegate_.get())
172    delegate_->OnPopupHidden(this);
173
174  if (view_)
175    view_->Hide();
176
177  delete this;
178}
179
180void AutofillPopupControllerImpl::ViewDestroyed() {
181  // The view has already been destroyed so clear the reference to it.
182  view_ = NULL;
183
184  Hide();
185}
186
187bool AutofillPopupControllerImpl::HandleKeyPressEvent(
188    const content::NativeWebKeyboardEvent& event) {
189  switch (event.windowsKeyCode) {
190    case ui::VKEY_UP:
191      SelectPreviousLine();
192      return true;
193    case ui::VKEY_DOWN:
194      SelectNextLine();
195      return true;
196    case ui::VKEY_PRIOR:  // Page up.
197      SetSelectedLine(0);
198      return true;
199    case ui::VKEY_NEXT:  // Page down.
200      SetSelectedLine(names().size() - 1);
201      return true;
202    case ui::VKEY_ESCAPE:
203      Hide();
204      return true;
205    case ui::VKEY_DELETE:
206      return (event.modifiers & content::NativeWebKeyboardEvent::ShiftKey) &&
207             RemoveSelectedLine();
208    case ui::VKEY_TAB:
209      // A tab press should cause the highlighted line to be selected, but still
210      // return false so the tab key press propagates and changes the cursor
211      // location.
212      AcceptSelectedLine();
213      return false;
214    case ui::VKEY_RETURN:
215      return AcceptSelectedLine();
216    default:
217      return false;
218  }
219}
220
221void AutofillPopupControllerImpl::UpdateBoundsAndRedrawPopup() {
222#if !defined(OS_ANDROID)
223  // TODO(csharp): Since UpdatePopupBounds can change the position of the popup,
224  // the popup could end up jumping from above the element to below it.
225  // It is unclear if it is better to keep the popup where it was, or if it
226  // should try and move to its desired position.
227  UpdatePopupBounds();
228#endif
229
230  view_->UpdateBoundsAndRedrawPopup();
231}
232
233void AutofillPopupControllerImpl::MouseHovered(int x, int y) {
234  SetSelectedLine(LineFromY(y));
235}
236
237void AutofillPopupControllerImpl::MouseClicked(int x, int y) {
238  MouseHovered(x, y);
239  AcceptSelectedLine();
240}
241
242void AutofillPopupControllerImpl::MouseExitedPopup() {
243  SetSelectedLine(kNoSelection);
244}
245
246void AutofillPopupControllerImpl::AcceptSuggestion(size_t index) {
247  delegate_->DidAcceptSuggestion(full_names_[index], identifiers_[index]);
248}
249
250int AutofillPopupControllerImpl::GetIconResourceID(
251    const string16& resource_name) {
252  for (size_t i = 0; i < arraysize(kDataResources); ++i) {
253    if (resource_name == ASCIIToUTF16(kDataResources[i].name))
254      return kDataResources[i].id;
255  }
256
257  return -1;
258}
259
260bool AutofillPopupControllerImpl::CanDelete(size_t index) const {
261  // TODO(isherman): Native AddressBook suggestions on Mac and Android should
262  // not be considered to be deleteable.
263  int id = identifiers_[index];
264  return id > 0 ||
265      id == WebAutofillClient::MenuItemIDAutocompleteEntry ||
266      id == WebAutofillClient::MenuItemIDPasswordEntry;
267}
268
269gfx::Rect AutofillPopupControllerImpl::GetRowBounds(size_t index) {
270  int top = 0;
271  for (size_t i = 0; i < index; ++i) {
272    top += GetRowHeightFromId(identifiers()[i]);
273  }
274
275  return gfx::Rect(
276      0,
277      top,
278      popup_bounds_.width(),
279      GetRowHeightFromId(identifiers()[index]));
280}
281
282void AutofillPopupControllerImpl::SetPopupBounds(const gfx::Rect& bounds) {
283  popup_bounds_ = bounds;
284  UpdateBoundsAndRedrawPopup();
285}
286
287const gfx::Rect& AutofillPopupControllerImpl::popup_bounds() const {
288  return popup_bounds_;
289}
290
291gfx::NativeView AutofillPopupControllerImpl::container_view() const {
292  return container_view_;
293}
294
295const gfx::RectF& AutofillPopupControllerImpl::element_bounds() const {
296  return element_bounds_;
297}
298
299const std::vector<string16>& AutofillPopupControllerImpl::names() const {
300  return names_;
301}
302
303const std::vector<string16>& AutofillPopupControllerImpl::subtexts() const {
304  return subtexts_;
305}
306
307const std::vector<string16>& AutofillPopupControllerImpl::icons() const {
308  return icons_;
309}
310
311const std::vector<int>& AutofillPopupControllerImpl::identifiers() const {
312  return identifiers_;
313}
314
315#if !defined(OS_ANDROID)
316const gfx::Font& AutofillPopupControllerImpl::GetNameFontForRow(size_t index)
317    const {
318  if (identifiers_[index] == WebAutofillClient::MenuItemIDWarningMessage)
319    return warning_font_;
320
321  return name_font_;
322}
323
324const gfx::Font& AutofillPopupControllerImpl::subtext_font() const {
325  return subtext_font_;
326}
327#endif
328
329int AutofillPopupControllerImpl::selected_line() const {
330  return selected_line_;
331}
332
333void AutofillPopupControllerImpl::SetSelectedLine(int selected_line) {
334  if (selected_line_ == selected_line)
335    return;
336
337  if (selected_line_ != kNoSelection &&
338      static_cast<size_t>(selected_line_) < identifiers_.size())
339    InvalidateRow(selected_line_);
340
341  if (selected_line != kNoSelection)
342    InvalidateRow(selected_line);
343
344  selected_line_ = selected_line;
345
346  if (selected_line_ != kNoSelection)
347    delegate_->DidSelectSuggestion(identifiers_[selected_line_]);
348  else
349    delegate_->ClearPreviewedForm();
350}
351
352void AutofillPopupControllerImpl::SelectNextLine() {
353  int new_selected_line = selected_line_ + 1;
354
355  // Skip over any lines that can't be selected.
356  while (static_cast<size_t>(new_selected_line) < names_.size() &&
357         !CanAccept(identifiers()[new_selected_line])) {
358    ++new_selected_line;
359  }
360
361  if (new_selected_line >= static_cast<int>(names_.size()))
362    new_selected_line = 0;
363
364  SetSelectedLine(new_selected_line);
365}
366
367void AutofillPopupControllerImpl::SelectPreviousLine() {
368  int new_selected_line = selected_line_ - 1;
369
370  // Skip over any lines that can't be selected.
371  while (new_selected_line > kNoSelection &&
372         !CanAccept(identifiers()[new_selected_line])) {
373    --new_selected_line;
374  }
375
376  if (new_selected_line <= kNoSelection)
377    new_selected_line = names_.size() - 1;
378
379  SetSelectedLine(new_selected_line);
380}
381
382bool AutofillPopupControllerImpl::AcceptSelectedLine() {
383  if (selected_line_ == kNoSelection)
384    return false;
385
386  DCHECK_GE(selected_line_, 0);
387  DCHECK_LT(selected_line_, static_cast<int>(names_.size()));
388
389  if (!CanAccept(identifiers_[selected_line_]))
390    return false;
391
392  AcceptSuggestion(selected_line_);
393  return true;
394}
395
396bool AutofillPopupControllerImpl::RemoveSelectedLine() {
397  if (selected_line_ == kNoSelection)
398    return false;
399
400  DCHECK_GE(selected_line_, 0);
401  DCHECK_LT(selected_line_, static_cast<int>(names_.size()));
402
403  if (!CanDelete(selected_line_))
404    return false;
405
406  delegate_->RemoveSuggestion(full_names_[selected_line_],
407                              identifiers_[selected_line_]);
408
409  // Remove the deleted element.
410  names_.erase(names_.begin() + selected_line_);
411  full_names_.erase(full_names_.begin() + selected_line_);
412  subtexts_.erase(subtexts_.begin() + selected_line_);
413  icons_.erase(icons_.begin() + selected_line_);
414  identifiers_.erase(identifiers_.begin() + selected_line_);
415
416  SetSelectedLine(kNoSelection);
417
418  if (HasSuggestions()) {
419    delegate_->ClearPreviewedForm();
420    UpdateBoundsAndRedrawPopup();
421  } else {
422    Hide();
423  }
424
425  return true;
426}
427
428int AutofillPopupControllerImpl::LineFromY(int y) {
429  int current_height = 0;
430
431  for (size_t i = 0; i < identifiers().size(); ++i) {
432    current_height += GetRowHeightFromId(identifiers()[i]);
433
434    if (y <= current_height)
435      return i;
436  }
437
438  // The y value goes beyond the popup so stop the selection at the last line.
439  return identifiers().size() - 1;
440}
441
442int AutofillPopupControllerImpl::GetRowHeightFromId(int identifier) const {
443  if (identifier == WebAutofillClient::MenuItemIDSeparator)
444    return kSeparatorHeight;
445
446  return kRowHeight;
447}
448
449bool AutofillPopupControllerImpl::CanAccept(int id) {
450  return id != WebAutofillClient::MenuItemIDSeparator &&
451      id != WebAutofillClient::MenuItemIDWarningMessage;
452}
453
454bool AutofillPopupControllerImpl::HasSuggestions() {
455  return identifiers_.size() != 0 &&
456      (identifiers_[0] > 0 ||
457       identifiers_[0] ==
458           WebAutofillClient::MenuItemIDAutocompleteEntry ||
459       identifiers_[0] == WebAutofillClient::MenuItemIDPasswordEntry ||
460       identifiers_[0] == WebAutofillClient::MenuItemIDDataListEntry);
461}
462
463void AutofillPopupControllerImpl::SetValues(
464    const std::vector<string16>& names,
465    const std::vector<string16>& subtexts,
466    const std::vector<string16>& icons,
467    const std::vector<int>& identifiers) {
468  names_ = names;
469  full_names_ = names;
470  subtexts_ = subtexts;
471  icons_ = icons;
472  identifiers_ = identifiers;
473}
474
475void AutofillPopupControllerImpl::ShowView() {
476  view_->Show();
477}
478
479void AutofillPopupControllerImpl::InvalidateRow(size_t row) {
480  DCHECK(0 <= row);
481  DCHECK(row < identifiers_.size());
482  view_->InvalidateRow(row);
483}
484
485#if !defined(OS_ANDROID)
486int AutofillPopupControllerImpl::GetDesiredPopupWidth() const {
487  if (!name_font_.platform_font() || !subtext_font_.platform_font()) {
488    // We can't calculate the size of the popup if the fonts
489    // aren't present.
490    return 0;
491  }
492
493  int popup_width = RoundedElementBounds().width();
494  DCHECK_EQ(names().size(), subtexts().size());
495  for (size_t i = 0; i < names().size(); ++i) {
496    int row_size = name_font_.GetStringWidth(names()[i]) +
497        subtext_font_.GetStringWidth(subtexts()[i]) +
498        RowWidthWithoutText(i);
499
500    popup_width = std::max(popup_width, row_size);
501  }
502
503  return popup_width;
504}
505
506int AutofillPopupControllerImpl::GetDesiredPopupHeight() const {
507  int popup_height = 0;
508
509  for (size_t i = 0; i < identifiers().size(); ++i) {
510    popup_height += GetRowHeightFromId(identifiers()[i]);
511  }
512
513  return popup_height;
514}
515
516int AutofillPopupControllerImpl::RowWidthWithoutText(int row) const {
517  int row_size = kEndPadding;
518
519  if (!subtexts_[row].empty())
520    row_size += kNamePadding;
521
522  // Add the Autofill icon size, if required.
523  if (!icons_[row].empty())
524    row_size += kAutofillIconWidth + kIconPadding;
525
526  // Add the padding at the end
527  row_size += kEndPadding;
528
529  return row_size;
530}
531
532void AutofillPopupControllerImpl::UpdatePopupBounds() {
533  int popup_required_width = GetDesiredPopupWidth();
534  int popup_height = GetDesiredPopupHeight();
535  // This is the top left point of the popup if the popup is above the element
536  // and grows to the left (since that is the highest and furthest left the
537  // popup go could).
538  gfx::Point top_left_corner_of_popup = RoundedElementBounds().origin() +
539      gfx::Vector2d(RoundedElementBounds().width() - popup_required_width,
540                    -popup_height);
541
542  // This is the bottom right point of the popup if the popup is below the
543  // element and grows to the right (since the is the lowest and furthest right
544  // the popup could go).
545  gfx::Point bottom_right_corner_of_popup = RoundedElementBounds().origin() +
546      gfx::Vector2d(popup_required_width,
547                    RoundedElementBounds().height() + popup_height);
548
549  gfx::Display top_left_display = GetDisplayNearestPoint(
550      top_left_corner_of_popup);
551  gfx::Display bottom_right_display = GetDisplayNearestPoint(
552      bottom_right_corner_of_popup);
553
554  std::pair<int, int> popup_x_and_width = CalculatePopupXAndWidth(
555      top_left_display, bottom_right_display, popup_required_width);
556  std::pair<int, int> popup_y_and_height = CalculatePopupYAndHeight(
557      top_left_display, bottom_right_display, popup_height);
558
559  popup_bounds_ = gfx::Rect(popup_x_and_width.first,
560                            popup_y_and_height.first,
561                            popup_x_and_width.second,
562                            popup_y_and_height.second);
563}
564#endif  // !defined(OS_ANDROID)
565
566WeakPtr<AutofillPopupControllerImpl> AutofillPopupControllerImpl::GetWeakPtr() {
567  return weak_ptr_factory_.GetWeakPtr();
568}
569
570void AutofillPopupControllerImpl::ClearState() {
571  // Don't clear view_, because otherwise the popup will have to get regenerated
572  // and this will cause flickering.
573
574  popup_bounds_ = gfx::Rect();
575
576  names_.clear();
577  subtexts_.clear();
578  icons_.clear();
579  identifiers_.clear();
580  full_names_.clear();
581
582  selected_line_ = kNoSelection;
583}
584
585const gfx::Rect AutofillPopupControllerImpl::RoundedElementBounds() const {
586  return gfx::ToEnclosingRect(element_bounds_);
587}
588
589gfx::Display AutofillPopupControllerImpl::GetDisplayNearestPoint(
590    const gfx::Point& point) const {
591  return gfx::Screen::GetScreenFor(container_view())->GetDisplayNearestPoint(
592      point);
593}
594
595std::pair<int, int> AutofillPopupControllerImpl::CalculatePopupXAndWidth(
596    const gfx::Display& left_display,
597    const gfx::Display& right_display,
598    int popup_required_width) const {
599  int leftmost_display_x = left_display.bounds().x() *
600      left_display.device_scale_factor();
601  int rightmost_display_x = right_display.GetSizeInPixel().width() +
602      right_display.bounds().x() * right_display.device_scale_factor();
603
604  // Calculate the start coordinates for the popup if it is growing right or
605  // the end position if it is growing to the left, capped to screen space.
606  int right_growth_start = std::max(leftmost_display_x,
607                                    std::min(rightmost_display_x,
608                                             RoundedElementBounds().x()));
609  int left_growth_end = std::max(leftmost_display_x,
610                                   std::min(rightmost_display_x,
611                                            RoundedElementBounds().right()));
612
613  int right_available = rightmost_display_x - right_growth_start;
614  int left_available = left_growth_end - leftmost_display_x;
615
616  int popup_width = std::min(popup_required_width,
617                             std::max(right_available, left_available));
618
619  // If there is enough space for the popup on the right, show it there,
620  // otherwise choose the larger size.
621  if (right_available >= popup_width || right_available >= left_available)
622    return std::make_pair(right_growth_start, popup_width);
623  else
624    return std::make_pair(left_growth_end - popup_width, popup_width);
625}
626
627std::pair<int,int> AutofillPopupControllerImpl::CalculatePopupYAndHeight(
628    const gfx::Display& top_display,
629    const gfx::Display& bottom_display,
630    int popup_required_height) const {
631  int topmost_display_y = top_display.bounds().y() *
632      top_display.device_scale_factor();
633  int bottommost_display_y = bottom_display.GetSizeInPixel().height() +
634      (bottom_display.bounds().y() *
635       bottom_display.device_scale_factor());
636
637  // Calculate the start coordinates for the popup if it is growing down or
638  // the end position if it is growing up, capped to screen space.
639  int top_growth_end = std::max(topmost_display_y,
640                                std::min(bottommost_display_y,
641                                         RoundedElementBounds().y()));
642  int bottom_growth_start = std::max(topmost_display_y,
643      std::min(bottommost_display_y, RoundedElementBounds().bottom()));
644
645  int top_available = bottom_growth_start - topmost_display_y;
646  int bottom_available = bottommost_display_y - top_growth_end;
647
648  // TODO(csharp): Restrict the popup height to what is available.
649  if (bottom_available >= popup_required_height ||
650      bottom_available >= top_available) {
651    // The popup can appear below the field.
652    return std::make_pair(bottom_growth_start, popup_required_height);
653  } else {
654    // The popup must appear above the field.
655    return std::make_pair(top_growth_end - popup_required_height,
656                          popup_required_height);
657  }
658}
659
660}  // namespace autofill
661