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