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