Upstream version 7.36.149.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 "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
26 using base::WeakPtr;
27
28 namespace autofill {
29 namespace {
30
31 // Used to indicate that no line is currently selected by the user.
32 const int kNoSelection = -1;
33
34 // The vertical height of each row in pixels.
35 const size_t kRowHeight = 24;
36
37 // The vertical height of a separator in pixels.
38 const size_t kSeparatorHeight = 1;
39
40 #if !defined(OS_ANDROID)
41 // Size difference between name and subtext in pixels.
42 const int kLabelFontSizeDelta = -2;
43
44 const size_t kNamePadding = AutofillPopupView::kNamePadding;
45 const size_t kIconPadding = AutofillPopupView::kIconPadding;
46 const size_t kEndPadding = AutofillPopupView::kEndPadding;
47 #endif
48
49 struct DataResource {
50   const char* name;
51   int id;
52 };
53
54 const 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
67 WeakPtr<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
93 AutofillPopupControllerImpl::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
121 AutofillPopupControllerImpl::~AutofillPopupControllerImpl() {}
122
123 void 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 receives 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   controller_common_->RegisterKeyPressCallback();
181   delegate_->OnPopupShown();
182 }
183
184 void 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
236 void 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
247 void AutofillPopupControllerImpl::ViewDestroyed() {
248   // The view has already been destroyed so clear the reference to it.
249   view_ = NULL;
250
251   Hide();
252 }
253
254 bool 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
288 void 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
300 void AutofillPopupControllerImpl::SetSelectionAtPoint(const gfx::Point& point) {
301   SetSelectedLine(LineFromY(point.y()));
302 }
303
304 bool 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
318 void AutofillPopupControllerImpl::SelectionCleared() {
319   SetSelectedLine(kNoSelection);
320 }
321
322 void AutofillPopupControllerImpl::AcceptSuggestion(size_t index) {
323   delegate_->DidAcceptSuggestion(full_names_[index], identifiers_[index]);
324 }
325
326 int 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
336 bool 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
344 bool AutofillPopupControllerImpl::IsWarning(size_t index) const {
345   return identifiers_[index] == POPUP_ITEM_ID_WARNING_MESSAGE;
346 }
347
348 gfx::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
361 void AutofillPopupControllerImpl::SetPopupBounds(const gfx::Rect& bounds) {
362   popup_bounds_ = bounds;
363   UpdateBoundsAndRedrawPopup();
364 }
365
366 const gfx::Rect& AutofillPopupControllerImpl::popup_bounds() const {
367   return popup_bounds_;
368 }
369
370 content::WebContents* AutofillPopupControllerImpl::web_contents() {
371   return controller_common_->web_contents();
372 }
373
374 gfx::NativeView AutofillPopupControllerImpl::container_view() {
375   return controller_common_->container_view();
376 }
377
378 const gfx::RectF& AutofillPopupControllerImpl::element_bounds() const {
379   return controller_common_->element_bounds();
380 }
381
382 bool AutofillPopupControllerImpl::IsRTL() const {
383   return text_direction_ == base::i18n::RIGHT_TO_LEFT;
384 }
385
386 const std::vector<base::string16>& AutofillPopupControllerImpl::names() const {
387   return names_;
388 }
389
390 const std::vector<base::string16>& AutofillPopupControllerImpl::subtexts()
391     const {
392   return subtexts_;
393 }
394
395 const std::vector<base::string16>& AutofillPopupControllerImpl::icons() const {
396   return icons_;
397 }
398
399 const std::vector<int>& AutofillPopupControllerImpl::identifiers() const {
400   return identifiers_;
401 }
402
403 #if !defined(OS_ANDROID)
404 const 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
412 const gfx::FontList& AutofillPopupControllerImpl::subtext_font_list() const {
413   return subtext_font_list_;
414 }
415 #endif
416
417 int AutofillPopupControllerImpl::selected_line() const {
418   return selected_line_;
419 }
420
421 void 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
442 void 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
457 void 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
472 bool 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
504 int 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
518 int AutofillPopupControllerImpl::GetRowHeightFromId(int identifier) const {
519   if (identifier == POPUP_ITEM_ID_SEPARATOR)
520     return kSeparatorHeight;
521
522   return kRowHeight;
523 }
524
525 bool AutofillPopupControllerImpl::CanAccept(int id) {
526   return id != POPUP_ITEM_ID_SEPARATOR && id != POPUP_ITEM_ID_WARNING_MESSAGE;
527 }
528
529 bool 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
537 void 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
549 void AutofillPopupControllerImpl::ShowView() {
550   view_->Show();
551 }
552
553 void 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)
560 int 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
575 int 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
585 int 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
607 void 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
616 WeakPtr<AutofillPopupControllerImpl> AutofillPopupControllerImpl::GetWeakPtr() {
617   return weak_ptr_factory_.GetWeakPtr();
618 }
619
620 void 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