| // Copyright (c) 2012 The Chromium Authors. All rights reserved. |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| #include "chrome/browser/ui/autofill/autofill_popup_controller_impl.h" |
| |
| #include <algorithm> |
| #include <utility> |
| |
| #include "base/logging.h" |
| #include "base/strings/utf_string_conversions.h" |
| #include "chrome/browser/ui/autofill/autofill_popup_view.h" |
| #include "components/autofill/core/browser/autofill_popup_delegate.h" |
| #include "content/public/browser/native_web_keyboard_event.h" |
| #include "grit/webkit_resources.h" |
| #include "third_party/WebKit/public/web/WebAutofillClient.h" |
| #include "ui/base/events/event.h" |
| #include "ui/base/text/text_elider.h" |
| #include "ui/gfx/display.h" |
| #include "ui/gfx/rect_conversions.h" |
| #include "ui/gfx/screen.h" |
| #include "ui/gfx/vector2d.h" |
| |
| using base::WeakPtr; |
| using WebKit::WebAutofillClient; |
| |
| namespace autofill { |
| namespace { |
| |
| // Used to indicate that no line is currently selected by the user. |
| const int kNoSelection = -1; |
| |
| // Size difference between name and subtext in pixels. |
| const int kLabelFontSizeDelta = -2; |
| |
| // The vertical height of each row in pixels. |
| const size_t kRowHeight = 24; |
| |
| // The vertical height of a separator in pixels. |
| const size_t kSeparatorHeight = 1; |
| |
| // The maximum amount of characters to display from either the name or subtext. |
| const size_t kMaxTextLength = 15; |
| |
| #if !defined(OS_ANDROID) |
| const size_t kNamePadding = AutofillPopupView::kNamePadding; |
| const size_t kIconPadding = AutofillPopupView::kIconPadding; |
| const size_t kEndPadding = AutofillPopupView::kEndPadding; |
| const size_t kAutofillIconWidth = AutofillPopupView::kAutofillIconWidth; |
| #endif |
| |
| struct DataResource { |
| const char* name; |
| int id; |
| }; |
| |
| const DataResource kDataResources[] = { |
| { "americanExpressCC", IDR_AUTOFILL_CC_AMEX }, |
| { "dinersCC", IDR_AUTOFILL_CC_DINERS }, |
| { "discoverCC", IDR_AUTOFILL_CC_DISCOVER }, |
| { "genericCC", IDR_AUTOFILL_CC_GENERIC }, |
| { "jcbCC", IDR_AUTOFILL_CC_JCB }, |
| { "masterCardCC", IDR_AUTOFILL_CC_MASTERCARD }, |
| { "visaCC", IDR_AUTOFILL_CC_VISA }, |
| }; |
| |
| } // namespace |
| |
| // static |
| WeakPtr<AutofillPopupControllerImpl> AutofillPopupControllerImpl::GetOrCreate( |
| WeakPtr<AutofillPopupControllerImpl> previous, |
| WeakPtr<AutofillPopupDelegate> delegate, |
| gfx::NativeView container_view, |
| const gfx::RectF& element_bounds, |
| base::i18n::TextDirection text_direction) { |
| DCHECK(!previous.get() || previous->delegate_.get() == delegate.get()); |
| |
| if (previous.get() && previous->container_view() == container_view && |
| previous->element_bounds() == element_bounds) { |
| previous->ClearState(); |
| return previous; |
| } |
| |
| if (previous.get()) |
| previous->Hide(); |
| |
| AutofillPopupControllerImpl* controller = |
| new AutofillPopupControllerImpl( |
| delegate, container_view, element_bounds, text_direction); |
| return controller->GetWeakPtr(); |
| } |
| |
| AutofillPopupControllerImpl::AutofillPopupControllerImpl( |
| base::WeakPtr<AutofillPopupDelegate> delegate, |
| gfx::NativeView container_view, |
| const gfx::RectF& element_bounds, |
| base::i18n::TextDirection text_direction) |
| : view_(NULL), |
| delegate_(delegate), |
| container_view_(container_view), |
| element_bounds_(element_bounds), |
| text_direction_(text_direction), |
| weak_ptr_factory_(this) { |
| ClearState(); |
| #if !defined(OS_ANDROID) |
| subtext_font_ = name_font_.DeriveFont(kLabelFontSizeDelta); |
| warning_font_ = name_font_.DeriveFont(0, gfx::Font::ITALIC); |
| #endif |
| } |
| |
| AutofillPopupControllerImpl::~AutofillPopupControllerImpl() {} |
| |
| void AutofillPopupControllerImpl::Show( |
| const std::vector<string16>& names, |
| const std::vector<string16>& subtexts, |
| const std::vector<string16>& icons, |
| const std::vector<int>& identifiers) { |
| SetValues(names, subtexts, icons, identifiers); |
| |
| #if !defined(OS_ANDROID) |
| // Android displays the long text with ellipsis using the view attributes. |
| |
| UpdatePopupBounds(); |
| int popup_width = popup_bounds().width(); |
| |
| // Elide the name and subtext strings so that the popup fits in the available |
| // space. |
| for (size_t i = 0; i < names_.size(); ++i) { |
| int name_width = GetNameFontForRow(i).GetStringWidth(names_[i]); |
| int subtext_width = subtext_font().GetStringWidth(subtexts_[i]); |
| int total_text_length = name_width + subtext_width; |
| |
| // The line can have no strings if it represents a UI element, such as |
| // a separator line. |
| if (total_text_length == 0) |
| continue; |
| |
| int available_width = popup_width - RowWidthWithoutText(i); |
| |
| // Each field recieves space in proportion to its length. |
| int name_size = available_width * name_width / total_text_length; |
| names_[i] = ui::ElideText(names_[i], |
| GetNameFontForRow(i), |
| name_size, |
| ui::ELIDE_AT_END); |
| |
| int subtext_size = available_width * subtext_width / total_text_length; |
| subtexts_[i] = ui::ElideText(subtexts_[i], |
| subtext_font(), |
| subtext_size, |
| ui::ELIDE_AT_END); |
| } |
| #endif |
| |
| if (!view_) { |
| view_ = AutofillPopupView::Create(this); |
| |
| // It is possible to fail to create the popup, in this case |
| // treat the popup as hiding right away. |
| if (!view_) { |
| Hide(); |
| return; |
| } |
| |
| ShowView(); |
| } else { |
| UpdateBoundsAndRedrawPopup(); |
| } |
| |
| delegate_->OnPopupShown(this); |
| } |
| |
| void AutofillPopupControllerImpl::UpdateDataListValues( |
| const std::vector<base::string16>& values, |
| const std::vector<base::string16>& labels) { |
| // Remove all the old data list values, which should always be at the top of |
| // the list if they are present. |
| while (!identifiers_.empty() && |
| identifiers_[0] == WebAutofillClient::MenuItemIDDataListEntry) { |
| names_.erase(names_.begin()); |
| subtexts_.erase(subtexts_.begin()); |
| icons_.erase(icons_.begin()); |
| identifiers_.erase(identifiers_.begin()); |
| } |
| |
| // If there are no new data list values, exit (clearing the separator if there |
| // is one). |
| if (values.empty()) { |
| if (!identifiers_.empty() && |
| identifiers_[0] == WebAutofillClient::MenuItemIDSeparator) { |
| names_.erase(names_.begin()); |
| subtexts_.erase(subtexts_.begin()); |
| icons_.erase(icons_.begin()); |
| identifiers_.erase(identifiers_.begin()); |
| } |
| |
| // The popup contents have changed, so either update the bounds or hide it. |
| if (HasSuggestions()) |
| UpdateBoundsAndRedrawPopup(); |
| else |
| Hide(); |
| |
| return; |
| } |
| |
| // Add a separator if there are any other values. |
| if (!identifiers_.empty() && |
| identifiers_[0] != WebAutofillClient::MenuItemIDSeparator) { |
| names_.insert(names_.begin(), string16()); |
| subtexts_.insert(subtexts_.begin(), string16()); |
| icons_.insert(icons_.begin(), string16()); |
| identifiers_.insert(identifiers_.begin(), |
| WebAutofillClient::MenuItemIDSeparator); |
| } |
| |
| |
| names_.insert(names_.begin(), values.begin(), values.end()); |
| subtexts_.insert(subtexts_.begin(), labels.begin(), labels.end()); |
| |
| // Add the values that are the same for all data list elements. |
| icons_.insert(icons_.begin(), values.size(), base::string16()); |
| identifiers_.insert(identifiers_.begin(), |
| values.size(), |
| WebAutofillClient::MenuItemIDDataListEntry); |
| |
| UpdateBoundsAndRedrawPopup(); |
| } |
| |
| void AutofillPopupControllerImpl::Hide() { |
| if (delegate_.get()) |
| delegate_->OnPopupHidden(this); |
| |
| if (view_) |
| view_->Hide(); |
| |
| delete this; |
| } |
| |
| void AutofillPopupControllerImpl::ViewDestroyed() { |
| // The view has already been destroyed so clear the reference to it. |
| view_ = NULL; |
| |
| Hide(); |
| } |
| |
| bool AutofillPopupControllerImpl::HandleKeyPressEvent( |
| const content::NativeWebKeyboardEvent& event) { |
| switch (event.windowsKeyCode) { |
| case ui::VKEY_UP: |
| SelectPreviousLine(); |
| return true; |
| case ui::VKEY_DOWN: |
| SelectNextLine(); |
| return true; |
| case ui::VKEY_PRIOR: // Page up. |
| SetSelectedLine(0); |
| return true; |
| case ui::VKEY_NEXT: // Page down. |
| SetSelectedLine(names().size() - 1); |
| return true; |
| case ui::VKEY_ESCAPE: |
| Hide(); |
| return true; |
| case ui::VKEY_DELETE: |
| return (event.modifiers & content::NativeWebKeyboardEvent::ShiftKey) && |
| RemoveSelectedLine(); |
| case ui::VKEY_TAB: |
| // A tab press should cause the highlighted line to be selected, but still |
| // return false so the tab key press propagates and changes the cursor |
| // location. |
| AcceptSelectedLine(); |
| return false; |
| case ui::VKEY_RETURN: |
| return AcceptSelectedLine(); |
| default: |
| return false; |
| } |
| } |
| |
| void AutofillPopupControllerImpl::UpdateBoundsAndRedrawPopup() { |
| #if !defined(OS_ANDROID) |
| // TODO(csharp): Since UpdatePopupBounds can change the position of the popup, |
| // the popup could end up jumping from above the element to below it. |
| // It is unclear if it is better to keep the popup where it was, or if it |
| // should try and move to its desired position. |
| UpdatePopupBounds(); |
| #endif |
| |
| view_->UpdateBoundsAndRedrawPopup(); |
| } |
| |
| void AutofillPopupControllerImpl::MouseHovered(int x, int y) { |
| SetSelectedLine(LineFromY(y)); |
| } |
| |
| void AutofillPopupControllerImpl::MouseClicked(int x, int y) { |
| MouseHovered(x, y); |
| AcceptSelectedLine(); |
| } |
| |
| void AutofillPopupControllerImpl::MouseExitedPopup() { |
| SetSelectedLine(kNoSelection); |
| } |
| |
| void AutofillPopupControllerImpl::AcceptSuggestion(size_t index) { |
| delegate_->DidAcceptSuggestion(full_names_[index], identifiers_[index]); |
| } |
| |
| int AutofillPopupControllerImpl::GetIconResourceID( |
| const string16& resource_name) { |
| for (size_t i = 0; i < arraysize(kDataResources); ++i) { |
| if (resource_name == ASCIIToUTF16(kDataResources[i].name)) |
| return kDataResources[i].id; |
| } |
| |
| return -1; |
| } |
| |
| bool AutofillPopupControllerImpl::CanDelete(size_t index) const { |
| // TODO(isherman): Native AddressBook suggestions on Mac and Android should |
| // not be considered to be deleteable. |
| int id = identifiers_[index]; |
| return id > 0 || |
| id == WebAutofillClient::MenuItemIDAutocompleteEntry || |
| id == WebAutofillClient::MenuItemIDPasswordEntry; |
| } |
| |
| bool AutofillPopupControllerImpl::IsWarning(size_t index) const { |
| return identifiers_[index] == WebAutofillClient::MenuItemIDWarningMessage; |
| } |
| |
| gfx::Rect AutofillPopupControllerImpl::GetRowBounds(size_t index) { |
| int top = 0; |
| for (size_t i = 0; i < index; ++i) { |
| top += GetRowHeightFromId(identifiers()[i]); |
| } |
| |
| return gfx::Rect( |
| 0, |
| top, |
| popup_bounds_.width(), |
| GetRowHeightFromId(identifiers()[index])); |
| } |
| |
| void AutofillPopupControllerImpl::SetPopupBounds(const gfx::Rect& bounds) { |
| popup_bounds_ = bounds; |
| UpdateBoundsAndRedrawPopup(); |
| } |
| |
| const gfx::Rect& AutofillPopupControllerImpl::popup_bounds() const { |
| return popup_bounds_; |
| } |
| |
| gfx::NativeView AutofillPopupControllerImpl::container_view() const { |
| return container_view_; |
| } |
| |
| const gfx::RectF& AutofillPopupControllerImpl::element_bounds() const { |
| return element_bounds_; |
| } |
| |
| bool AutofillPopupControllerImpl::IsRTL() const { |
| return text_direction_ == base::i18n::RIGHT_TO_LEFT; |
| } |
| |
| const std::vector<string16>& AutofillPopupControllerImpl::names() const { |
| return names_; |
| } |
| |
| const std::vector<string16>& AutofillPopupControllerImpl::subtexts() const { |
| return subtexts_; |
| } |
| |
| const std::vector<string16>& AutofillPopupControllerImpl::icons() const { |
| return icons_; |
| } |
| |
| const std::vector<int>& AutofillPopupControllerImpl::identifiers() const { |
| return identifiers_; |
| } |
| |
| #if !defined(OS_ANDROID) |
| const gfx::Font& AutofillPopupControllerImpl::GetNameFontForRow(size_t index) |
| const { |
| if (identifiers_[index] == WebAutofillClient::MenuItemIDWarningMessage) |
| return warning_font_; |
| |
| return name_font_; |
| } |
| |
| const gfx::Font& AutofillPopupControllerImpl::subtext_font() const { |
| return subtext_font_; |
| } |
| #endif |
| |
| int AutofillPopupControllerImpl::selected_line() const { |
| return selected_line_; |
| } |
| |
| void AutofillPopupControllerImpl::SetSelectedLine(int selected_line) { |
| if (selected_line_ == selected_line) |
| return; |
| |
| if (selected_line_ != kNoSelection && |
| static_cast<size_t>(selected_line_) < identifiers_.size()) |
| InvalidateRow(selected_line_); |
| |
| if (selected_line != kNoSelection) |
| InvalidateRow(selected_line); |
| |
| selected_line_ = selected_line; |
| |
| if (selected_line_ != kNoSelection) |
| delegate_->DidSelectSuggestion(identifiers_[selected_line_]); |
| else |
| delegate_->ClearPreviewedForm(); |
| } |
| |
| void AutofillPopupControllerImpl::SelectNextLine() { |
| int new_selected_line = selected_line_ + 1; |
| |
| // Skip over any lines that can't be selected. |
| while (static_cast<size_t>(new_selected_line) < names_.size() && |
| !CanAccept(identifiers()[new_selected_line])) { |
| ++new_selected_line; |
| } |
| |
| if (new_selected_line >= static_cast<int>(names_.size())) |
| new_selected_line = 0; |
| |
| SetSelectedLine(new_selected_line); |
| } |
| |
| void AutofillPopupControllerImpl::SelectPreviousLine() { |
| int new_selected_line = selected_line_ - 1; |
| |
| // Skip over any lines that can't be selected. |
| while (new_selected_line > kNoSelection && |
| !CanAccept(identifiers()[new_selected_line])) { |
| --new_selected_line; |
| } |
| |
| if (new_selected_line <= kNoSelection) |
| new_selected_line = names_.size() - 1; |
| |
| SetSelectedLine(new_selected_line); |
| } |
| |
| bool AutofillPopupControllerImpl::AcceptSelectedLine() { |
| if (selected_line_ == kNoSelection) |
| return false; |
| |
| DCHECK_GE(selected_line_, 0); |
| DCHECK_LT(selected_line_, static_cast<int>(names_.size())); |
| |
| if (!CanAccept(identifiers_[selected_line_])) |
| return false; |
| |
| AcceptSuggestion(selected_line_); |
| return true; |
| } |
| |
| bool AutofillPopupControllerImpl::RemoveSelectedLine() { |
| if (selected_line_ == kNoSelection) |
| return false; |
| |
| DCHECK_GE(selected_line_, 0); |
| DCHECK_LT(selected_line_, static_cast<int>(names_.size())); |
| |
| if (!CanDelete(selected_line_)) |
| return false; |
| |
| delegate_->RemoveSuggestion(full_names_[selected_line_], |
| identifiers_[selected_line_]); |
| |
| // Remove the deleted element. |
| names_.erase(names_.begin() + selected_line_); |
| full_names_.erase(full_names_.begin() + selected_line_); |
| subtexts_.erase(subtexts_.begin() + selected_line_); |
| icons_.erase(icons_.begin() + selected_line_); |
| identifiers_.erase(identifiers_.begin() + selected_line_); |
| |
| SetSelectedLine(kNoSelection); |
| |
| if (HasSuggestions()) { |
| delegate_->ClearPreviewedForm(); |
| UpdateBoundsAndRedrawPopup(); |
| } else { |
| Hide(); |
| } |
| |
| return true; |
| } |
| |
| int AutofillPopupControllerImpl::LineFromY(int y) { |
| int current_height = 0; |
| |
| for (size_t i = 0; i < identifiers().size(); ++i) { |
| current_height += GetRowHeightFromId(identifiers()[i]); |
| |
| if (y <= current_height) |
| return i; |
| } |
| |
| // The y value goes beyond the popup so stop the selection at the last line. |
| return identifiers().size() - 1; |
| } |
| |
| int AutofillPopupControllerImpl::GetRowHeightFromId(int identifier) const { |
| if (identifier == WebAutofillClient::MenuItemIDSeparator) |
| return kSeparatorHeight; |
| |
| return kRowHeight; |
| } |
| |
| bool AutofillPopupControllerImpl::CanAccept(int id) { |
| return id != WebAutofillClient::MenuItemIDSeparator && |
| id != WebAutofillClient::MenuItemIDWarningMessage; |
| } |
| |
| bool AutofillPopupControllerImpl::HasSuggestions() { |
| return identifiers_.size() != 0 && |
| (identifiers_[0] > 0 || |
| identifiers_[0] == |
| WebAutofillClient::MenuItemIDAutocompleteEntry || |
| identifiers_[0] == WebAutofillClient::MenuItemIDPasswordEntry || |
| identifiers_[0] == WebAutofillClient::MenuItemIDDataListEntry); |
| } |
| |
| void AutofillPopupControllerImpl::SetValues( |
| const std::vector<string16>& names, |
| const std::vector<string16>& subtexts, |
| const std::vector<string16>& icons, |
| const std::vector<int>& identifiers) { |
| names_ = names; |
| full_names_ = names; |
| subtexts_ = subtexts; |
| icons_ = icons; |
| identifiers_ = identifiers; |
| } |
| |
| void AutofillPopupControllerImpl::ShowView() { |
| view_->Show(); |
| } |
| |
| void AutofillPopupControllerImpl::InvalidateRow(size_t row) { |
| DCHECK(0 <= row); |
| DCHECK(row < identifiers_.size()); |
| view_->InvalidateRow(row); |
| } |
| |
| #if !defined(OS_ANDROID) |
| int AutofillPopupControllerImpl::GetDesiredPopupWidth() const { |
| if (!name_font_.platform_font() || !subtext_font_.platform_font()) { |
| // We can't calculate the size of the popup if the fonts |
| // aren't present. |
| return 0; |
| } |
| |
| int popup_width = RoundedElementBounds().width(); |
| DCHECK_EQ(names().size(), subtexts().size()); |
| for (size_t i = 0; i < names().size(); ++i) { |
| int row_size = name_font_.GetStringWidth(names()[i]) + |
| subtext_font_.GetStringWidth(subtexts()[i]) + |
| RowWidthWithoutText(i); |
| |
| popup_width = std::max(popup_width, row_size); |
| } |
| |
| return popup_width; |
| } |
| |
| int AutofillPopupControllerImpl::GetDesiredPopupHeight() const { |
| int popup_height = 0; |
| |
| for (size_t i = 0; i < identifiers().size(); ++i) { |
| popup_height += GetRowHeightFromId(identifiers()[i]); |
| } |
| |
| return popup_height; |
| } |
| |
| int AutofillPopupControllerImpl::RowWidthWithoutText(int row) const { |
| int row_size = kEndPadding; |
| |
| if (!subtexts_[row].empty()) |
| row_size += kNamePadding; |
| |
| // Add the Autofill icon size, if required. |
| if (!icons_[row].empty()) |
| row_size += kAutofillIconWidth + kIconPadding; |
| |
| // Add the padding at the end |
| row_size += kEndPadding; |
| |
| return row_size; |
| } |
| |
| void AutofillPopupControllerImpl::UpdatePopupBounds() { |
| int popup_required_width = GetDesiredPopupWidth(); |
| int popup_height = GetDesiredPopupHeight(); |
| // This is the top left point of the popup if the popup is above the element |
| // and grows to the left (since that is the highest and furthest left the |
| // popup go could). |
| gfx::Point top_left_corner_of_popup = RoundedElementBounds().origin() + |
| gfx::Vector2d(RoundedElementBounds().width() - popup_required_width, |
| -popup_height); |
| |
| // This is the bottom right point of the popup if the popup is below the |
| // element and grows to the right (since the is the lowest and furthest right |
| // the popup could go). |
| gfx::Point bottom_right_corner_of_popup = RoundedElementBounds().origin() + |
| gfx::Vector2d(popup_required_width, |
| RoundedElementBounds().height() + popup_height); |
| |
| gfx::Display top_left_display = GetDisplayNearestPoint( |
| top_left_corner_of_popup); |
| gfx::Display bottom_right_display = GetDisplayNearestPoint( |
| bottom_right_corner_of_popup); |
| |
| std::pair<int, int> popup_x_and_width = CalculatePopupXAndWidth( |
| top_left_display, bottom_right_display, popup_required_width); |
| std::pair<int, int> popup_y_and_height = CalculatePopupYAndHeight( |
| top_left_display, bottom_right_display, popup_height); |
| |
| popup_bounds_ = gfx::Rect(popup_x_and_width.first, |
| popup_y_and_height.first, |
| popup_x_and_width.second, |
| popup_y_and_height.second); |
| } |
| #endif // !defined(OS_ANDROID) |
| |
| WeakPtr<AutofillPopupControllerImpl> AutofillPopupControllerImpl::GetWeakPtr() { |
| return weak_ptr_factory_.GetWeakPtr(); |
| } |
| |
| void AutofillPopupControllerImpl::ClearState() { |
| // Don't clear view_, because otherwise the popup will have to get regenerated |
| // and this will cause flickering. |
| |
| popup_bounds_ = gfx::Rect(); |
| |
| names_.clear(); |
| subtexts_.clear(); |
| icons_.clear(); |
| identifiers_.clear(); |
| full_names_.clear(); |
| |
| selected_line_ = kNoSelection; |
| } |
| |
| const gfx::Rect AutofillPopupControllerImpl::RoundedElementBounds() const { |
| return gfx::ToEnclosingRect(element_bounds_); |
| } |
| |
| gfx::Display AutofillPopupControllerImpl::GetDisplayNearestPoint( |
| const gfx::Point& point) const { |
| return gfx::Screen::GetScreenFor(container_view())->GetDisplayNearestPoint( |
| point); |
| } |
| |
| std::pair<int, int> AutofillPopupControllerImpl::CalculatePopupXAndWidth( |
| const gfx::Display& left_display, |
| const gfx::Display& right_display, |
| int popup_required_width) const { |
| int leftmost_display_x = left_display.bounds().x(); |
| int rightmost_display_x = |
| right_display.GetSizeInPixel().width() + right_display.bounds().x(); |
| |
| // Calculate the start coordinates for the popup if it is growing right or |
| // the end position if it is growing to the left, capped to screen space. |
| int right_growth_start = std::max(leftmost_display_x, |
| std::min(rightmost_display_x, |
| RoundedElementBounds().x())); |
| int left_growth_end = std::max(leftmost_display_x, |
| std::min(rightmost_display_x, |
| RoundedElementBounds().right())); |
| |
| int right_available = rightmost_display_x - right_growth_start; |
| int left_available = left_growth_end - leftmost_display_x; |
| |
| int popup_width = std::min(popup_required_width, |
| std::max(right_available, left_available)); |
| |
| // If there is enough space for the popup on the right, show it there, |
| // otherwise choose the larger size. |
| if (right_available >= popup_width || right_available >= left_available) |
| return std::make_pair(right_growth_start, popup_width); |
| else |
| return std::make_pair(left_growth_end - popup_width, popup_width); |
| } |
| |
| std::pair<int,int> AutofillPopupControllerImpl::CalculatePopupYAndHeight( |
| const gfx::Display& top_display, |
| const gfx::Display& bottom_display, |
| int popup_required_height) const { |
| int topmost_display_y = top_display.bounds().y(); |
| int bottommost_display_y = |
| bottom_display.GetSizeInPixel().height() + bottom_display.bounds().y(); |
| |
| // Calculate the start coordinates for the popup if it is growing down or |
| // the end position if it is growing up, capped to screen space. |
| int top_growth_end = std::max(topmost_display_y, |
| std::min(bottommost_display_y, |
| RoundedElementBounds().y())); |
| int bottom_growth_start = std::max(topmost_display_y, |
| std::min(bottommost_display_y, RoundedElementBounds().bottom())); |
| |
| int top_available = bottom_growth_start - topmost_display_y; |
| int bottom_available = bottommost_display_y - top_growth_end; |
| |
| // TODO(csharp): Restrict the popup height to what is available. |
| if (bottom_available >= popup_required_height || |
| bottom_available >= top_available) { |
| // The popup can appear below the field. |
| return std::make_pair(bottom_growth_start, popup_required_height); |
| } else { |
| // The popup must appear above the field. |
| return std::make_pair(top_growth_end - popup_required_height, |
| popup_required_height); |
| } |
| } |
| |
| } // namespace autofill |