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/core/browser/autofill_popup_delegate.h"
14 #include "content/public/browser/native_web_keyboard_event.h"
15 #include "content/public/browser/render_view_host.h"
16 #include "content/public/browser/web_contents.h"
17 #include "grit/webkit_resources.h"
18 #include "third_party/WebKit/public/web/WebAutofillClient.h"
19 #include "ui/base/resource/resource_bundle.h"
20 #include "ui/events/event.h"
21 #include "ui/gfx/display.h"
22 #include "ui/gfx/rect_conversions.h"
23 #include "ui/gfx/screen.h"
24 #include "ui/gfx/text_elider.h"
25 #include "ui/gfx/vector2d.h"
26
27 using base::WeakPtr;
28 using blink::WebAutofillClient;
29
30 namespace autofill {
31 namespace {
32
33 // Used to indicate that no line is currently selected by the user.
34 const int kNoSelection = -1;
35
36 // The vertical height of each row in pixels.
37 const size_t kRowHeight = 24;
38
39 // The vertical height of a separator in pixels.
40 const size_t kSeparatorHeight = 1;
41
42 #if !defined(OS_ANDROID)
43 // Size difference between name and subtext in pixels.
44 const int kLabelFontSizeDelta = -2;
45
46 const size_t kNamePadding = AutofillPopupView::kNamePadding;
47 const size_t kIconPadding = AutofillPopupView::kIconPadding;
48 const size_t kEndPadding = AutofillPopupView::kEndPadding;
49 #endif
50
51 struct DataResource {
52 const char* name;
53 int id;
54 };
55
56 const DataResource kDataResources[] = {
57 { "americanExpressCC", IDR_AUTOFILL_CC_AMEX },
58 { "dinersCC", IDR_AUTOFILL_CC_DINERS },
59 { "discoverCC", IDR_AUTOFILL_CC_DISCOVER },
60 { "genericCC", IDR_AUTOFILL_CC_GENERIC },
61 { "jcbCC", IDR_AUTOFILL_CC_JCB },
62 { "masterCardCC", IDR_AUTOFILL_CC_MASTERCARD },
63 { "visaCC", IDR_AUTOFILL_CC_VISA },
64 };
65
66 } // namespace
67
68 // static
GetOrCreate(WeakPtr<AutofillPopupControllerImpl> previous,WeakPtr<AutofillPopupDelegate> delegate,content::WebContents * web_contents,gfx::NativeView container_view,const gfx::RectF & element_bounds,base::i18n::TextDirection text_direction)69 WeakPtr<AutofillPopupControllerImpl> AutofillPopupControllerImpl::GetOrCreate(
70 WeakPtr<AutofillPopupControllerImpl> previous,
71 WeakPtr<AutofillPopupDelegate> delegate,
72 content::WebContents* web_contents,
73 gfx::NativeView container_view,
74 const gfx::RectF& element_bounds,
75 base::i18n::TextDirection text_direction) {
76 DCHECK(!previous.get() || previous->delegate_.get() == delegate.get());
77
78 if (previous.get() && previous->web_contents_ == web_contents &&
79 previous->container_view() == container_view &&
80 previous->element_bounds() == element_bounds) {
81 previous->ClearState();
82 return previous;
83 }
84
85 if (previous.get())
86 previous->Hide();
87
88 AutofillPopupControllerImpl* controller =
89 new AutofillPopupControllerImpl(
90 delegate, web_contents, container_view, element_bounds,
91 text_direction);
92 return controller->GetWeakPtr();
93 }
94
AutofillPopupControllerImpl(base::WeakPtr<AutofillPopupDelegate> delegate,content::WebContents * web_contents,gfx::NativeView container_view,const gfx::RectF & element_bounds,base::i18n::TextDirection text_direction)95 AutofillPopupControllerImpl::AutofillPopupControllerImpl(
96 base::WeakPtr<AutofillPopupDelegate> delegate,
97 content::WebContents* web_contents,
98 gfx::NativeView container_view,
99 const gfx::RectF& element_bounds,
100 base::i18n::TextDirection text_direction)
101 : view_(NULL),
102 delegate_(delegate),
103 web_contents_(web_contents),
104 container_view_(container_view),
105 element_bounds_(element_bounds),
106 text_direction_(text_direction),
107 registered_key_press_event_callback_with_(NULL),
108 hide_on_outside_click_(false),
109 key_press_event_callback_(
110 base::Bind(&AutofillPopupControllerImpl::HandleKeyPressEvent,
111 base::Unretained(this))),
112 weak_ptr_factory_(this) {
113 ClearState();
114 #if !defined(OS_ANDROID)
115 subtext_font_ = name_font_.DeriveFont(kLabelFontSizeDelta);
116 #if defined(OS_MACOSX)
117 // There is no italic version of the system font.
118 warning_font_ = name_font_;
119 #else
120 warning_font_ = name_font_.DeriveFont(0, gfx::Font::ITALIC);
121 #endif
122 #endif
123 }
124
~AutofillPopupControllerImpl()125 AutofillPopupControllerImpl::~AutofillPopupControllerImpl() {}
126
Show(const std::vector<base::string16> & names,const std::vector<base::string16> & subtexts,const std::vector<base::string16> & icons,const std::vector<int> & identifiers)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 = GetNameFontForRow(i).GetStringWidth(names_[i]);
144 int subtext_width = subtext_font().GetStringWidth(subtexts_[i]);
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 GetNameFontForRow(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(),
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 if (web_contents_ && !registered_key_press_event_callback_with_) {
186 registered_key_press_event_callback_with_ =
187 web_contents_->GetRenderViewHost();
188 registered_key_press_event_callback_with_->AddKeyPressEventCallback(
189 key_press_event_callback_);
190 }
191 }
192
UpdateDataListValues(const std::vector<base::string16> & values,const std::vector<base::string16> & labels)193 void AutofillPopupControllerImpl::UpdateDataListValues(
194 const std::vector<base::string16>& values,
195 const std::vector<base::string16>& labels) {
196 // Remove all the old data list values, which should always be at the top of
197 // the list if they are present.
198 while (!identifiers_.empty() &&
199 identifiers_[0] == WebAutofillClient::MenuItemIDDataListEntry) {
200 names_.erase(names_.begin());
201 subtexts_.erase(subtexts_.begin());
202 icons_.erase(icons_.begin());
203 identifiers_.erase(identifiers_.begin());
204 }
205
206 // If there are no new data list values, exit (clearing the separator if there
207 // is one).
208 if (values.empty()) {
209 if (!identifiers_.empty() &&
210 identifiers_[0] == WebAutofillClient::MenuItemIDSeparator) {
211 names_.erase(names_.begin());
212 subtexts_.erase(subtexts_.begin());
213 icons_.erase(icons_.begin());
214 identifiers_.erase(identifiers_.begin());
215 }
216
217 // The popup contents have changed, so either update the bounds or hide it.
218 if (HasSuggestions())
219 UpdateBoundsAndRedrawPopup();
220 else
221 Hide();
222
223 return;
224 }
225
226 // Add a separator if there are any other values.
227 if (!identifiers_.empty() &&
228 identifiers_[0] != WebAutofillClient::MenuItemIDSeparator) {
229 names_.insert(names_.begin(), base::string16());
230 subtexts_.insert(subtexts_.begin(), base::string16());
231 icons_.insert(icons_.begin(), base::string16());
232 identifiers_.insert(identifiers_.begin(),
233 WebAutofillClient::MenuItemIDSeparator);
234 }
235
236
237 names_.insert(names_.begin(), values.begin(), values.end());
238 subtexts_.insert(subtexts_.begin(), labels.begin(), labels.end());
239
240 // Add the values that are the same for all data list elements.
241 icons_.insert(icons_.begin(), values.size(), base::string16());
242 identifiers_.insert(identifiers_.begin(),
243 values.size(),
244 WebAutofillClient::MenuItemIDDataListEntry);
245
246 UpdateBoundsAndRedrawPopup();
247 }
248
Hide()249 void AutofillPopupControllerImpl::Hide() {
250 if (web_contents_ && (!web_contents_->IsBeingDestroyed()) &&
251 (registered_key_press_event_callback_with_ ==
252 web_contents_->GetRenderViewHost())) {
253 web_contents_->GetRenderViewHost()->RemoveKeyPressEventCallback(
254 key_press_event_callback_);
255 }
256 registered_key_press_event_callback_with_ = NULL;
257
258 if (delegate_.get())
259 delegate_->OnPopupHidden();
260
261 if (view_)
262 view_->Hide();
263
264 delete this;
265 }
266
ViewDestroyed()267 void AutofillPopupControllerImpl::ViewDestroyed() {
268 // The view has already been destroyed so clear the reference to it.
269 view_ = NULL;
270
271 Hide();
272 }
273
HandleKeyPressEvent(const content::NativeWebKeyboardEvent & event)274 bool AutofillPopupControllerImpl::HandleKeyPressEvent(
275 const content::NativeWebKeyboardEvent& event) {
276 switch (event.windowsKeyCode) {
277 case ui::VKEY_UP:
278 SelectPreviousLine();
279 return true;
280 case ui::VKEY_DOWN:
281 SelectNextLine();
282 return true;
283 case ui::VKEY_PRIOR: // Page up.
284 SetSelectedLine(0);
285 return true;
286 case ui::VKEY_NEXT: // Page down.
287 SetSelectedLine(names().size() - 1);
288 return true;
289 case ui::VKEY_ESCAPE:
290 Hide();
291 return true;
292 case ui::VKEY_DELETE:
293 return (event.modifiers & content::NativeWebKeyboardEvent::ShiftKey) &&
294 RemoveSelectedLine();
295 case ui::VKEY_TAB:
296 // A tab press should cause the selected line to be accepted, but still
297 // return false so the tab key press propagates and changes the cursor
298 // location.
299 AcceptSelectedLine();
300 return false;
301 case ui::VKEY_RETURN:
302 return AcceptSelectedLine();
303 default:
304 return false;
305 }
306 }
307
UpdateBoundsAndRedrawPopup()308 void AutofillPopupControllerImpl::UpdateBoundsAndRedrawPopup() {
309 #if !defined(OS_ANDROID)
310 // TODO(csharp): Since UpdatePopupBounds can change the position of the popup,
311 // the popup could end up jumping from above the element to below it.
312 // It is unclear if it is better to keep the popup where it was, or if it
313 // should try and move to its desired position.
314 UpdatePopupBounds();
315 #endif
316
317 view_->UpdateBoundsAndRedrawPopup();
318 }
319
LineSelectedAtPoint(int x,int y)320 void AutofillPopupControllerImpl::LineSelectedAtPoint(int x, int y) {
321 SetSelectedLine(LineFromY(y));
322 }
323
LineAcceptedAtPoint(int x,int y)324 void AutofillPopupControllerImpl::LineAcceptedAtPoint(int x, int y) {
325 LineSelectedAtPoint(x, y);
326 AcceptSelectedLine();
327 }
328
SelectionCleared()329 void AutofillPopupControllerImpl::SelectionCleared() {
330 SetSelectedLine(kNoSelection);
331 }
332
ShouldRepostEvent(const ui::MouseEvent & event)333 bool AutofillPopupControllerImpl::ShouldRepostEvent(
334 const ui::MouseEvent& event) {
335 return delegate_->ShouldRepostEvent(event);
336 }
337
AcceptSuggestion(size_t index)338 void AutofillPopupControllerImpl::AcceptSuggestion(size_t index) {
339 delegate_->DidAcceptSuggestion(full_names_[index], identifiers_[index]);
340 }
341
GetIconResourceID(const base::string16 & resource_name) const342 int AutofillPopupControllerImpl::GetIconResourceID(
343 const base::string16& resource_name) const {
344 for (size_t i = 0; i < arraysize(kDataResources); ++i) {
345 if (resource_name == ASCIIToUTF16(kDataResources[i].name))
346 return kDataResources[i].id;
347 }
348
349 return -1;
350 }
351
CanDelete(size_t index) const352 bool AutofillPopupControllerImpl::CanDelete(size_t index) const {
353 // TODO(isherman): Native AddressBook suggestions on Mac and Android should
354 // not be considered to be deleteable.
355 int id = identifiers_[index];
356 return id > 0 ||
357 id == WebAutofillClient::MenuItemIDAutocompleteEntry ||
358 id == WebAutofillClient::MenuItemIDPasswordEntry;
359 }
360
IsWarning(size_t index) const361 bool AutofillPopupControllerImpl::IsWarning(size_t index) const {
362 return identifiers_[index] == WebAutofillClient::MenuItemIDWarningMessage;
363 }
364
GetRowBounds(size_t index)365 gfx::Rect AutofillPopupControllerImpl::GetRowBounds(size_t index) {
366 int top = AutofillPopupView::kBorderThickness;
367 for (size_t i = 0; i < index; ++i) {
368 top += GetRowHeightFromId(identifiers()[i]);
369 }
370
371 return gfx::Rect(
372 AutofillPopupView::kBorderThickness,
373 top,
374 popup_bounds_.width() - 2 * AutofillPopupView::kBorderThickness,
375 GetRowHeightFromId(identifiers()[index]));
376 }
377
SetPopupBounds(const gfx::Rect & bounds)378 void AutofillPopupControllerImpl::SetPopupBounds(const gfx::Rect& bounds) {
379 popup_bounds_ = bounds;
380 UpdateBoundsAndRedrawPopup();
381 }
382
popup_bounds() const383 const gfx::Rect& AutofillPopupControllerImpl::popup_bounds() const {
384 return popup_bounds_;
385 }
386
container_view() const387 gfx::NativeView AutofillPopupControllerImpl::container_view() const {
388 return container_view_;
389 }
390
element_bounds() const391 const gfx::RectF& AutofillPopupControllerImpl::element_bounds() const {
392 return element_bounds_;
393 }
394
IsRTL() const395 bool AutofillPopupControllerImpl::IsRTL() const {
396 return text_direction_ == base::i18n::RIGHT_TO_LEFT;
397 }
398
hide_on_outside_click() const399 bool AutofillPopupControllerImpl::hide_on_outside_click() const {
400 return hide_on_outside_click_;
401 }
402
names() const403 const std::vector<base::string16>& AutofillPopupControllerImpl::names() const {
404 return names_;
405 }
406
subtexts() const407 const std::vector<base::string16>& AutofillPopupControllerImpl::subtexts()
408 const {
409 return subtexts_;
410 }
411
icons() const412 const std::vector<base::string16>& AutofillPopupControllerImpl::icons() const {
413 return icons_;
414 }
415
identifiers() const416 const std::vector<int>& AutofillPopupControllerImpl::identifiers() const {
417 return identifiers_;
418 }
419
420 #if !defined(OS_ANDROID)
GetNameFontForRow(size_t index) const421 const gfx::Font& AutofillPopupControllerImpl::GetNameFontForRow(size_t index)
422 const {
423 if (identifiers_[index] == WebAutofillClient::MenuItemIDWarningMessage)
424 return warning_font_;
425
426 return name_font_;
427 }
428
subtext_font() const429 const gfx::Font& AutofillPopupControllerImpl::subtext_font() const {
430 return subtext_font_;
431 }
432 #endif
433
selected_line() const434 int AutofillPopupControllerImpl::selected_line() const {
435 return selected_line_;
436 }
437
set_hide_on_outside_click(bool hide_on_outside_click)438 void AutofillPopupControllerImpl::set_hide_on_outside_click(
439 bool hide_on_outside_click) {
440 hide_on_outside_click_ = hide_on_outside_click;
441 }
442
SetSelectedLine(int selected_line)443 void AutofillPopupControllerImpl::SetSelectedLine(int selected_line) {
444 if (selected_line_ == selected_line)
445 return;
446
447 if (selected_line_ != kNoSelection &&
448 static_cast<size_t>(selected_line_) < identifiers_.size())
449 InvalidateRow(selected_line_);
450
451 if (selected_line != kNoSelection)
452 InvalidateRow(selected_line);
453
454 selected_line_ = selected_line;
455
456 if (selected_line_ != kNoSelection)
457 delegate_->DidSelectSuggestion(identifiers_[selected_line_]);
458 else
459 delegate_->ClearPreviewedForm();
460 }
461
SelectNextLine()462 void AutofillPopupControllerImpl::SelectNextLine() {
463 int new_selected_line = selected_line_ + 1;
464
465 // Skip over any lines that can't be selected.
466 while (static_cast<size_t>(new_selected_line) < names_.size() &&
467 !CanAccept(identifiers()[new_selected_line])) {
468 ++new_selected_line;
469 }
470
471 if (new_selected_line >= static_cast<int>(names_.size()))
472 new_selected_line = 0;
473
474 SetSelectedLine(new_selected_line);
475 }
476
SelectPreviousLine()477 void AutofillPopupControllerImpl::SelectPreviousLine() {
478 int new_selected_line = selected_line_ - 1;
479
480 // Skip over any lines that can't be selected.
481 while (new_selected_line > kNoSelection &&
482 !CanAccept(identifiers()[new_selected_line])) {
483 --new_selected_line;
484 }
485
486 if (new_selected_line <= kNoSelection)
487 new_selected_line = names_.size() - 1;
488
489 SetSelectedLine(new_selected_line);
490 }
491
AcceptSelectedLine()492 bool AutofillPopupControllerImpl::AcceptSelectedLine() {
493 if (selected_line_ == kNoSelection)
494 return false;
495
496 DCHECK_GE(selected_line_, 0);
497 DCHECK_LT(selected_line_, static_cast<int>(names_.size()));
498
499 if (!CanAccept(identifiers_[selected_line_]))
500 return false;
501
502 AcceptSuggestion(selected_line_);
503 return true;
504 }
505
RemoveSelectedLine()506 bool AutofillPopupControllerImpl::RemoveSelectedLine() {
507 if (selected_line_ == kNoSelection)
508 return false;
509
510 DCHECK_GE(selected_line_, 0);
511 DCHECK_LT(selected_line_, static_cast<int>(names_.size()));
512
513 if (!CanDelete(selected_line_))
514 return false;
515
516 delegate_->RemoveSuggestion(full_names_[selected_line_],
517 identifiers_[selected_line_]);
518
519 // Remove the deleted element.
520 names_.erase(names_.begin() + selected_line_);
521 full_names_.erase(full_names_.begin() + selected_line_);
522 subtexts_.erase(subtexts_.begin() + selected_line_);
523 icons_.erase(icons_.begin() + selected_line_);
524 identifiers_.erase(identifiers_.begin() + selected_line_);
525
526 SetSelectedLine(kNoSelection);
527
528 if (HasSuggestions()) {
529 delegate_->ClearPreviewedForm();
530 UpdateBoundsAndRedrawPopup();
531 } else {
532 Hide();
533 }
534
535 return true;
536 }
537
LineFromY(int y)538 int AutofillPopupControllerImpl::LineFromY(int y) {
539 int current_height = AutofillPopupView::kBorderThickness;
540
541 for (size_t i = 0; i < identifiers().size(); ++i) {
542 current_height += GetRowHeightFromId(identifiers()[i]);
543
544 if (y <= current_height)
545 return i;
546 }
547
548 // The y value goes beyond the popup so stop the selection at the last line.
549 return identifiers().size() - 1;
550 }
551
GetRowHeightFromId(int identifier) const552 int AutofillPopupControllerImpl::GetRowHeightFromId(int identifier) const {
553 if (identifier == WebAutofillClient::MenuItemIDSeparator)
554 return kSeparatorHeight;
555
556 return kRowHeight;
557 }
558
CanAccept(int id)559 bool AutofillPopupControllerImpl::CanAccept(int id) {
560 return id != WebAutofillClient::MenuItemIDSeparator &&
561 id != WebAutofillClient::MenuItemIDWarningMessage;
562 }
563
HasSuggestions()564 bool AutofillPopupControllerImpl::HasSuggestions() {
565 return identifiers_.size() != 0 &&
566 (identifiers_[0] > 0 ||
567 identifiers_[0] ==
568 WebAutofillClient::MenuItemIDAutocompleteEntry ||
569 identifiers_[0] == WebAutofillClient::MenuItemIDPasswordEntry ||
570 identifiers_[0] == WebAutofillClient::MenuItemIDDataListEntry);
571 }
572
SetValues(const std::vector<base::string16> & names,const std::vector<base::string16> & subtexts,const std::vector<base::string16> & icons,const std::vector<int> & identifiers)573 void AutofillPopupControllerImpl::SetValues(
574 const std::vector<base::string16>& names,
575 const std::vector<base::string16>& subtexts,
576 const std::vector<base::string16>& icons,
577 const std::vector<int>& identifiers) {
578 names_ = names;
579 full_names_ = names;
580 subtexts_ = subtexts;
581 icons_ = icons;
582 identifiers_ = identifiers;
583 }
584
ShowView()585 void AutofillPopupControllerImpl::ShowView() {
586 view_->Show();
587 }
588
InvalidateRow(size_t row)589 void AutofillPopupControllerImpl::InvalidateRow(size_t row) {
590 DCHECK(0 <= row);
591 DCHECK(row < identifiers_.size());
592 view_->InvalidateRow(row);
593 }
594
595 #if !defined(OS_ANDROID)
GetDesiredPopupWidth() const596 int AutofillPopupControllerImpl::GetDesiredPopupWidth() const {
597 if (!name_font_.platform_font() || !subtext_font_.platform_font()) {
598 // We can't calculate the size of the popup if the fonts
599 // aren't present.
600 return 0;
601 }
602
603 int popup_width = RoundedElementBounds().width();
604 DCHECK_EQ(names().size(), subtexts().size());
605 for (size_t i = 0; i < names().size(); ++i) {
606 int row_size = name_font_.GetStringWidth(names()[i]) +
607 subtext_font_.GetStringWidth(subtexts()[i]) +
608 RowWidthWithoutText(i);
609
610 popup_width = std::max(popup_width, row_size);
611 }
612
613 return popup_width;
614 }
615
GetDesiredPopupHeight() const616 int AutofillPopupControllerImpl::GetDesiredPopupHeight() const {
617 int popup_height = 2 * AutofillPopupView::kBorderThickness;
618
619 for (size_t i = 0; i < identifiers().size(); ++i) {
620 popup_height += GetRowHeightFromId(identifiers()[i]);
621 }
622
623 return popup_height;
624 }
625
RowWidthWithoutText(int row) const626 int AutofillPopupControllerImpl::RowWidthWithoutText(int row) const {
627 int row_size = kEndPadding;
628
629 if (!subtexts_[row].empty())
630 row_size += kNamePadding;
631
632 // Add the Autofill icon size, if required.
633 if (!icons_[row].empty()) {
634 int icon_width = ui::ResourceBundle::GetSharedInstance().GetImageNamed(
635 GetIconResourceID(icons_[row])).Width();
636 row_size += icon_width + kIconPadding;
637 }
638
639 // Add the padding at the end.
640 row_size += kEndPadding;
641
642 // Add room for the popup border.
643 row_size += 2 * AutofillPopupView::kBorderThickness;
644
645 return row_size;
646 }
647
UpdatePopupBounds()648 void AutofillPopupControllerImpl::UpdatePopupBounds() {
649 int popup_required_width = GetDesiredPopupWidth();
650 int popup_height = GetDesiredPopupHeight();
651 // This is the top left point of the popup if the popup is above the element
652 // and grows to the left (since that is the highest and furthest left the
653 // popup go could).
654 gfx::Point top_left_corner_of_popup = RoundedElementBounds().origin() +
655 gfx::Vector2d(RoundedElementBounds().width() - popup_required_width,
656 -popup_height);
657
658 // This is the bottom right point of the popup if the popup is below the
659 // element and grows to the right (since the is the lowest and furthest right
660 // the popup could go).
661 gfx::Point bottom_right_corner_of_popup = RoundedElementBounds().origin() +
662 gfx::Vector2d(popup_required_width,
663 RoundedElementBounds().height() + popup_height);
664
665 gfx::Display top_left_display = GetDisplayNearestPoint(
666 top_left_corner_of_popup);
667 gfx::Display bottom_right_display = GetDisplayNearestPoint(
668 bottom_right_corner_of_popup);
669
670 std::pair<int, int> popup_x_and_width = CalculatePopupXAndWidth(
671 top_left_display, bottom_right_display, popup_required_width);
672 std::pair<int, int> popup_y_and_height = CalculatePopupYAndHeight(
673 top_left_display, bottom_right_display, popup_height);
674
675 popup_bounds_ = gfx::Rect(popup_x_and_width.first,
676 popup_y_and_height.first,
677 popup_x_and_width.second,
678 popup_y_and_height.second);
679 }
680 #endif // !defined(OS_ANDROID)
681
GetWeakPtr()682 WeakPtr<AutofillPopupControllerImpl> AutofillPopupControllerImpl::GetWeakPtr() {
683 return weak_ptr_factory_.GetWeakPtr();
684 }
685
ClearState()686 void AutofillPopupControllerImpl::ClearState() {
687 // Don't clear view_, because otherwise the popup will have to get regenerated
688 // and this will cause flickering.
689
690 popup_bounds_ = gfx::Rect();
691
692 names_.clear();
693 subtexts_.clear();
694 icons_.clear();
695 identifiers_.clear();
696 full_names_.clear();
697
698 selected_line_ = kNoSelection;
699 }
700
RoundedElementBounds() const701 const gfx::Rect AutofillPopupControllerImpl::RoundedElementBounds() const {
702 return gfx::ToEnclosingRect(element_bounds_);
703 }
704
GetDisplayNearestPoint(const gfx::Point & point) const705 gfx::Display AutofillPopupControllerImpl::GetDisplayNearestPoint(
706 const gfx::Point& point) const {
707 return gfx::Screen::GetScreenFor(container_view())->GetDisplayNearestPoint(
708 point);
709 }
710
CalculatePopupXAndWidth(const gfx::Display & left_display,const gfx::Display & right_display,int popup_required_width) const711 std::pair<int, int> AutofillPopupControllerImpl::CalculatePopupXAndWidth(
712 const gfx::Display& left_display,
713 const gfx::Display& right_display,
714 int popup_required_width) const {
715 int leftmost_display_x = left_display.bounds().x();
716 int rightmost_display_x =
717 right_display.GetSizeInPixel().width() + right_display.bounds().x();
718
719 // Calculate the start coordinates for the popup if it is growing right or
720 // the end position if it is growing to the left, capped to screen space.
721 int right_growth_start = std::max(leftmost_display_x,
722 std::min(rightmost_display_x,
723 RoundedElementBounds().x()));
724 int left_growth_end = std::max(leftmost_display_x,
725 std::min(rightmost_display_x,
726 RoundedElementBounds().right()));
727
728 int right_available = rightmost_display_x - right_growth_start;
729 int left_available = left_growth_end - leftmost_display_x;
730
731 int popup_width = std::min(popup_required_width,
732 std::max(right_available, left_available));
733
734 // If there is enough space for the popup on the right, show it there,
735 // otherwise choose the larger size.
736 if (right_available >= popup_width || right_available >= left_available)
737 return std::make_pair(right_growth_start, popup_width);
738 else
739 return std::make_pair(left_growth_end - popup_width, popup_width);
740 }
741
CalculatePopupYAndHeight(const gfx::Display & top_display,const gfx::Display & bottom_display,int popup_required_height) const742 std::pair<int,int> AutofillPopupControllerImpl::CalculatePopupYAndHeight(
743 const gfx::Display& top_display,
744 const gfx::Display& bottom_display,
745 int popup_required_height) const {
746 int topmost_display_y = top_display.bounds().y();
747 int bottommost_display_y =
748 bottom_display.GetSizeInPixel().height() + bottom_display.bounds().y();
749
750 // Calculate the start coordinates for the popup if it is growing down or
751 // the end position if it is growing up, capped to screen space.
752 int top_growth_end = std::max(topmost_display_y,
753 std::min(bottommost_display_y,
754 RoundedElementBounds().y()));
755 int bottom_growth_start = std::max(topmost_display_y,
756 std::min(bottommost_display_y, RoundedElementBounds().bottom()));
757
758 int top_available = bottom_growth_start - topmost_display_y;
759 int bottom_available = bottommost_display_y - top_growth_end;
760
761 // TODO(csharp): Restrict the popup height to what is available.
762 if (bottom_available >= popup_required_height ||
763 bottom_available >= top_available) {
764 // The popup can appear below the field.
765 return std::make_pair(bottom_growth_start, popup_required_height);
766 } else {
767 // The popup must appear above the field.
768 return std::make_pair(top_growth_end - popup_required_height,
769 popup_required_height);
770 }
771 }
772
773 } // namespace autofill
774