Upstream version 11.40.277.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/components_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 #if defined(OS_MACOSX) && !defined(OS_IOS)
63   { "macContactsIcon", IDR_AUTOFILL_MAC_CONTACTS_ICON },
64 #endif  // defined(OS_MACOSX) && !defined(OS_IOS)
65 };
66
67 }  // namespace
68
69 // static
70 WeakPtr<AutofillPopupControllerImpl> AutofillPopupControllerImpl::GetOrCreate(
71     WeakPtr<AutofillPopupControllerImpl> previous,
72     WeakPtr<AutofillPopupDelegate> delegate,
73     content::WebContents* web_contents,
74     gfx::NativeView container_view,
75     const gfx::RectF& element_bounds,
76     base::i18n::TextDirection text_direction) {
77   DCHECK(!previous.get() || previous->delegate_.get() == delegate.get());
78
79   if (previous.get() && previous->web_contents() == web_contents &&
80       previous->container_view() == container_view &&
81       previous->element_bounds() == element_bounds) {
82     previous->ClearState();
83     return previous;
84   }
85
86   if (previous.get())
87     previous->Hide();
88
89   AutofillPopupControllerImpl* controller =
90       new AutofillPopupControllerImpl(
91           delegate, web_contents, container_view, element_bounds,
92           text_direction);
93   return controller->GetWeakPtr();
94 }
95
96 AutofillPopupControllerImpl::AutofillPopupControllerImpl(
97     base::WeakPtr<AutofillPopupDelegate> delegate,
98     content::WebContents* web_contents,
99     gfx::NativeView container_view,
100     const gfx::RectF& element_bounds,
101     base::i18n::TextDirection text_direction)
102     : controller_common_(new PopupControllerCommon(element_bounds,
103                                                    container_view,
104                                                    web_contents)),
105       view_(NULL),
106       delegate_(delegate),
107       text_direction_(text_direction),
108       weak_ptr_factory_(this) {
109   ClearState();
110   controller_common_->SetKeyPressCallback(
111       base::Bind(&AutofillPopupControllerImpl::HandleKeyPressEvent,
112                  base::Unretained(this)));
113 #if !defined(OS_ANDROID)
114   subtext_font_list_ = name_font_list_.DeriveWithSizeDelta(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_.DeriveWithStyle(gfx::Font::ITALIC);
120 #endif
121 #endif
122 }
123
124 AutofillPopupControllerImpl::~AutofillPopupControllerImpl() {}
125
126 void AutofillPopupControllerImpl::Show(
127     const std::vector<base::string16>& names,
128     const std::vector<base::string16>& subtexts,
129     const std::vector<base::string16>& icons,
130     const std::vector<int>& identifiers) {
131   SetValues(names, subtexts, icons, identifiers);
132
133 #if !defined(OS_ANDROID)
134   // Android displays the long text with ellipsis using the view attributes.
135
136   UpdatePopupBounds();
137   int popup_width = popup_bounds().width();
138
139   // Elide the name and subtext strings so that the popup fits in the available
140   // space.
141   for (size_t i = 0; i < names_.size(); ++i) {
142     int name_width = gfx::GetStringWidth(names_[i], GetNameFontListForRow(i));
143     int subtext_width = gfx::GetStringWidth(subtexts_[i], subtext_font_list());
144     int total_text_length = name_width + subtext_width;
145
146     // The line can have no strings if it represents a UI element, such as
147     // a separator line.
148     if (total_text_length == 0)
149       continue;
150
151     int available_width = popup_width - RowWidthWithoutText(i);
152
153     // Each field receives space in proportion to its length.
154     int name_size = available_width * name_width / total_text_length;
155     names_[i] = gfx::ElideText(names_[i], GetNameFontListForRow(i),
156                                name_size, gfx::ELIDE_TAIL);
157
158     int subtext_size = available_width * subtext_width / total_text_length;
159     subtexts_[i] = gfx::ElideText(subtexts_[i], subtext_font_list(),
160                                   subtext_size, gfx::ELIDE_TAIL);
161   }
162 #endif
163
164   if (!view_) {
165     view_ = AutofillPopupView::Create(this);
166
167     // It is possible to fail to create the popup, in this case
168     // treat the popup as hiding right away.
169     if (!view_) {
170       Hide();
171       return;
172     }
173
174     ShowView();
175   } else {
176     UpdateBoundsAndRedrawPopup();
177   }
178
179   controller_common_->RegisterKeyPressCallback();
180   delegate_->OnPopupShown();
181 }
182
183 void AutofillPopupControllerImpl::UpdateDataListValues(
184     const std::vector<base::string16>& values,
185     const std::vector<base::string16>& labels) {
186   // Remove all the old data list values, which should always be at the top of
187   // the list if they are present.
188   while (!identifiers_.empty() &&
189          identifiers_[0] == POPUP_ITEM_ID_DATALIST_ENTRY) {
190     names_.erase(names_.begin());
191     subtexts_.erase(subtexts_.begin());
192     icons_.erase(icons_.begin());
193     identifiers_.erase(identifiers_.begin());
194   }
195
196   // If there are no new data list values, exit (clearing the separator if there
197   // is one).
198   if (values.empty()) {
199     if (!identifiers_.empty() && identifiers_[0] == POPUP_ITEM_ID_SEPARATOR) {
200       names_.erase(names_.begin());
201       subtexts_.erase(subtexts_.begin());
202       icons_.erase(icons_.begin());
203       identifiers_.erase(identifiers_.begin());
204     }
205
206      // The popup contents have changed, so either update the bounds or hide it.
207     if (HasSuggestions())
208       UpdateBoundsAndRedrawPopup();
209     else
210       Hide();
211
212     return;
213   }
214
215   // Add a separator if there are any other values.
216   if (!identifiers_.empty() && identifiers_[0] != POPUP_ITEM_ID_SEPARATOR) {
217     names_.insert(names_.begin(), base::string16());
218     subtexts_.insert(subtexts_.begin(), base::string16());
219     icons_.insert(icons_.begin(), base::string16());
220     identifiers_.insert(identifiers_.begin(), POPUP_ITEM_ID_SEPARATOR);
221   }
222
223
224   names_.insert(names_.begin(), values.begin(), values.end());
225   subtexts_.insert(subtexts_.begin(), labels.begin(), labels.end());
226
227   // Add the values that are the same for all data list elements.
228   icons_.insert(icons_.begin(), values.size(), base::string16());
229   identifiers_.insert(
230       identifiers_.begin(), values.size(), POPUP_ITEM_ID_DATALIST_ENTRY);
231
232   UpdateBoundsAndRedrawPopup();
233 }
234
235 void AutofillPopupControllerImpl::Hide() {
236   controller_common_->RemoveKeyPressCallback();
237   if (delegate_)
238     delegate_->OnPopupHidden();
239
240   if (view_)
241     view_->Hide();
242
243   delete this;
244 }
245
246 void AutofillPopupControllerImpl::ViewDestroyed() {
247   // The view has already been destroyed so clear the reference to it.
248   view_ = NULL;
249
250   Hide();
251 }
252
253 bool AutofillPopupControllerImpl::HandleKeyPressEvent(
254     const content::NativeWebKeyboardEvent& event) {
255   switch (event.windowsKeyCode) {
256     case ui::VKEY_UP:
257       SelectPreviousLine();
258       return true;
259     case ui::VKEY_DOWN:
260       SelectNextLine();
261       return true;
262     case ui::VKEY_PRIOR:  // Page up.
263       SetSelectedLine(0);
264       return true;
265     case ui::VKEY_NEXT:  // Page down.
266       SetSelectedLine(names().size() - 1);
267       return true;
268     case ui::VKEY_ESCAPE:
269       Hide();
270       return true;
271     case ui::VKEY_DELETE:
272       return (event.modifiers & content::NativeWebKeyboardEvent::ShiftKey) &&
273              RemoveSelectedLine();
274     case ui::VKEY_TAB:
275       // A tab press should cause the selected line to be accepted, but still
276       // return false so the tab key press propagates and changes the cursor
277       // location.
278       AcceptSelectedLine();
279       return false;
280     case ui::VKEY_RETURN:
281       return AcceptSelectedLine();
282     default:
283       return false;
284   }
285 }
286
287 void AutofillPopupControllerImpl::UpdateBoundsAndRedrawPopup() {
288 #if !defined(OS_ANDROID)
289   // TODO(csharp): Since UpdatePopupBounds can change the position of the popup,
290   // the popup could end up jumping from above the element to below it.
291   // It is unclear if it is better to keep the popup where it was, or if it
292   // should try and move to its desired position.
293   UpdatePopupBounds();
294 #endif
295
296   view_->UpdateBoundsAndRedrawPopup();
297 }
298
299 void AutofillPopupControllerImpl::SetSelectionAtPoint(const gfx::Point& point) {
300   SetSelectedLine(LineFromY(point.y()));
301 }
302
303 bool AutofillPopupControllerImpl::AcceptSelectedLine() {
304   if (selected_line_ == kNoSelection)
305     return false;
306
307   DCHECK_GE(selected_line_, 0);
308   DCHECK_LT(selected_line_, static_cast<int>(names_.size()));
309
310   if (!CanAccept(identifiers_[selected_line_]))
311     return false;
312
313   AcceptSuggestion(selected_line_);
314   return true;
315 }
316
317 void AutofillPopupControllerImpl::SelectionCleared() {
318   SetSelectedLine(kNoSelection);
319 }
320
321 void AutofillPopupControllerImpl::AcceptSuggestion(size_t index) {
322   delegate_->DidAcceptSuggestion(full_names_[index], identifiers_[index]);
323 }
324
325 int AutofillPopupControllerImpl::GetIconResourceID(
326     const base::string16& resource_name) const {
327   for (size_t i = 0; i < arraysize(kDataResources); ++i) {
328     if (resource_name == base::ASCIIToUTF16(kDataResources[i].name))
329       return kDataResources[i].id;
330   }
331
332   return -1;
333 }
334
335 bool AutofillPopupControllerImpl::CanDelete(size_t index) const {
336   // TODO(isherman): Native AddressBook suggestions on Mac and Android should
337   // not be considered to be deleteable.
338   int id = identifiers_[index];
339   return id > 0 || id == POPUP_ITEM_ID_AUTOCOMPLETE_ENTRY ||
340          id == POPUP_ITEM_ID_PASSWORD_ENTRY;
341 }
342
343 bool AutofillPopupControllerImpl::IsWarning(size_t index) const {
344   return identifiers_[index] == POPUP_ITEM_ID_WARNING_MESSAGE;
345 }
346
347 gfx::Rect AutofillPopupControllerImpl::GetRowBounds(size_t index) {
348   int top = kPopupBorderThickness;
349   for (size_t i = 0; i < index; ++i) {
350     top += GetRowHeightFromId(identifiers()[i]);
351   }
352
353   return gfx::Rect(
354       kPopupBorderThickness,
355       top,
356       popup_bounds_.width() - 2 * kPopupBorderThickness,
357       GetRowHeightFromId(identifiers()[index]));
358 }
359
360 void AutofillPopupControllerImpl::SetPopupBounds(const gfx::Rect& bounds) {
361   popup_bounds_ = bounds;
362   UpdateBoundsAndRedrawPopup();
363 }
364
365 const gfx::Rect& AutofillPopupControllerImpl::popup_bounds() const {
366   return popup_bounds_;
367 }
368
369 content::WebContents* AutofillPopupControllerImpl::web_contents() {
370   return controller_common_->web_contents();
371 }
372
373 gfx::NativeView AutofillPopupControllerImpl::container_view() {
374   return controller_common_->container_view();
375 }
376
377 const gfx::RectF& AutofillPopupControllerImpl::element_bounds() const {
378   return controller_common_->element_bounds();
379 }
380
381 bool AutofillPopupControllerImpl::IsRTL() const {
382   return text_direction_ == base::i18n::RIGHT_TO_LEFT;
383 }
384
385 const std::vector<base::string16>& AutofillPopupControllerImpl::names() const {
386   return names_;
387 }
388
389 const std::vector<base::string16>& AutofillPopupControllerImpl::subtexts()
390     const {
391   return subtexts_;
392 }
393
394 const std::vector<base::string16>& AutofillPopupControllerImpl::icons() const {
395   return icons_;
396 }
397
398 const std::vector<int>& AutofillPopupControllerImpl::identifiers() const {
399   return identifiers_;
400 }
401
402 #if !defined(OS_ANDROID)
403 const gfx::FontList& AutofillPopupControllerImpl::GetNameFontListForRow(
404     size_t index) const {
405   if (identifiers_[index] == POPUP_ITEM_ID_WARNING_MESSAGE)
406     return warning_font_list_;
407
408   return name_font_list_;
409 }
410
411 const gfx::FontList& AutofillPopupControllerImpl::subtext_font_list() const {
412   return subtext_font_list_;
413 }
414 #endif
415
416 int AutofillPopupControllerImpl::selected_line() const {
417   return selected_line_;
418 }
419
420 void AutofillPopupControllerImpl::SetSelectedLine(int selected_line) {
421   if (selected_line_ == selected_line)
422     return;
423
424   if (selected_line_ != kNoSelection &&
425       static_cast<size_t>(selected_line_) < identifiers_.size())
426     InvalidateRow(selected_line_);
427
428   if (selected_line != kNoSelection)
429     InvalidateRow(selected_line);
430
431   selected_line_ = selected_line;
432
433   if (selected_line_ != kNoSelection) {
434     delegate_->DidSelectSuggestion(names_[selected_line_],
435                                    identifiers_[selected_line_]);
436   } else {
437     delegate_->ClearPreviewedForm();
438   }
439 }
440
441 void AutofillPopupControllerImpl::SelectNextLine() {
442   int new_selected_line = selected_line_ + 1;
443
444   // Skip over any lines that can't be selected.
445   while (static_cast<size_t>(new_selected_line) < names_.size() &&
446          !CanAccept(identifiers()[new_selected_line])) {
447     ++new_selected_line;
448   }
449
450   if (new_selected_line >= static_cast<int>(names_.size()))
451     new_selected_line = 0;
452
453   SetSelectedLine(new_selected_line);
454 }
455
456 void AutofillPopupControllerImpl::SelectPreviousLine() {
457   int new_selected_line = selected_line_ - 1;
458
459   // Skip over any lines that can't be selected.
460   while (new_selected_line > kNoSelection &&
461          !CanAccept(identifiers()[new_selected_line])) {
462     --new_selected_line;
463   }
464
465   if (new_selected_line <= kNoSelection)
466     new_selected_line = names_.size() - 1;
467
468   SetSelectedLine(new_selected_line);
469 }
470
471 bool AutofillPopupControllerImpl::RemoveSelectedLine() {
472   if (selected_line_ == kNoSelection)
473     return false;
474
475   DCHECK_GE(selected_line_, 0);
476   DCHECK_LT(selected_line_, static_cast<int>(names_.size()));
477
478   if (!CanDelete(selected_line_))
479     return false;
480
481   delegate_->RemoveSuggestion(full_names_[selected_line_],
482                               identifiers_[selected_line_]);
483
484   // Remove the deleted element.
485   names_.erase(names_.begin() + selected_line_);
486   full_names_.erase(full_names_.begin() + selected_line_);
487   subtexts_.erase(subtexts_.begin() + selected_line_);
488   icons_.erase(icons_.begin() + selected_line_);
489   identifiers_.erase(identifiers_.begin() + selected_line_);
490
491   SetSelectedLine(kNoSelection);
492
493   if (HasSuggestions()) {
494     delegate_->ClearPreviewedForm();
495     UpdateBoundsAndRedrawPopup();
496   } else {
497     Hide();
498   }
499
500   return true;
501 }
502
503 int AutofillPopupControllerImpl::LineFromY(int y) {
504   int current_height = kPopupBorderThickness;
505
506   for (size_t i = 0; i < identifiers().size(); ++i) {
507     current_height += GetRowHeightFromId(identifiers()[i]);
508
509     if (y <= current_height)
510       return i;
511   }
512
513   // The y value goes beyond the popup so stop the selection at the last line.
514   return identifiers().size() - 1;
515 }
516
517 int AutofillPopupControllerImpl::GetRowHeightFromId(int identifier) const {
518   if (identifier == POPUP_ITEM_ID_SEPARATOR)
519     return kSeparatorHeight;
520
521   return kRowHeight;
522 }
523
524 bool AutofillPopupControllerImpl::CanAccept(int id) {
525   return id != POPUP_ITEM_ID_SEPARATOR && id != POPUP_ITEM_ID_WARNING_MESSAGE;
526 }
527
528 bool AutofillPopupControllerImpl::HasSuggestions() {
529   return identifiers_.size() != 0 &&
530          (identifiers_[0] > 0 ||
531           identifiers_[0] == POPUP_ITEM_ID_AUTOCOMPLETE_ENTRY ||
532           identifiers_[0] == POPUP_ITEM_ID_PASSWORD_ENTRY ||
533           identifiers_[0] == POPUP_ITEM_ID_DATALIST_ENTRY ||
534           identifiers_[0] == POPUP_ITEM_ID_MAC_ACCESS_CONTACTS);
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_width = GetDesiredPopupWidth();
609   int popup_height = GetDesiredPopupHeight();
610
611   popup_bounds_ = controller_common_->GetPopupBounds(popup_width,
612                                                      popup_height);
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