1// Copyright 2014 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/popup_controller_common.h"
6
7#include <algorithm>
8#include <utility>
9
10#include "content/public/browser/render_view_host.h"
11#include "content/public/browser/web_contents.h"
12#include "ui/gfx/display.h"
13#include "ui/gfx/rect_conversions.h"
14#include "ui/gfx/screen.h"
15#include "ui/gfx/vector2d.h"
16
17namespace autofill {
18
19PopupControllerCommon::PopupControllerCommon(
20    const gfx::RectF& element_bounds,
21    const gfx::NativeView container_view,
22    content::WebContents* web_contents)
23    : element_bounds_(element_bounds),
24      container_view_(container_view),
25      web_contents_(web_contents),
26      key_press_event_target_(NULL) {}
27PopupControllerCommon::~PopupControllerCommon() {}
28
29void PopupControllerCommon::SetKeyPressCallback(
30    content::RenderWidgetHost::KeyPressEventCallback callback) {
31  DCHECK(key_press_event_callback_.is_null());
32  key_press_event_callback_ = callback;
33}
34
35void PopupControllerCommon::RegisterKeyPressCallback() {
36  if (web_contents_ && !key_press_event_target_) {
37    key_press_event_target_ = web_contents_->GetRenderViewHost();
38    key_press_event_target_->AddKeyPressEventCallback(
39        key_press_event_callback_);
40  }
41}
42
43void PopupControllerCommon::RemoveKeyPressCallback() {
44  if (web_contents_ && (!web_contents_->IsBeingDestroyed()) &&
45      key_press_event_target_ == web_contents_->GetRenderViewHost()) {
46    web_contents_->GetRenderViewHost()->RemoveKeyPressEventCallback(
47        key_press_event_callback_);
48  }
49  key_press_event_target_ = NULL;
50}
51
52gfx::Display PopupControllerCommon::GetDisplayNearestPoint(
53    const gfx::Point& point) const {
54  return gfx::Screen::GetScreenFor(container_view_)->GetDisplayNearestPoint(
55      point);
56}
57
58const gfx::Rect PopupControllerCommon::RoundedElementBounds() const {
59  return gfx::ToEnclosingRect(element_bounds_);
60}
61
62std::pair<int, int> PopupControllerCommon::CalculatePopupXAndWidth(
63    const gfx::Display& left_display,
64    const gfx::Display& right_display,
65    int popup_required_width) const {
66  int leftmost_display_x = left_display.bounds().x();
67  int rightmost_display_x =
68      right_display.GetSizeInPixel().width() + right_display.bounds().x();
69
70  // Calculate the start coordinates for the popup if it is growing right or
71  // the end position if it is growing to the left, capped to screen space.
72  int right_growth_start = std::max(leftmost_display_x,
73                                    std::min(rightmost_display_x,
74                                             RoundedElementBounds().x()));
75  int left_growth_end = std::max(leftmost_display_x,
76                                 std::min(rightmost_display_x,
77                                          RoundedElementBounds().right()));
78
79  int right_available = rightmost_display_x - right_growth_start;
80  int left_available = left_growth_end - leftmost_display_x;
81
82  int popup_width = std::min(popup_required_width,
83                             std::max(right_available, left_available));
84
85  // If there is enough space for the popup on the right, show it there,
86  // otherwise choose the larger size.
87  if (right_available >= popup_width || right_available >= left_available)
88    return std::make_pair(right_growth_start, popup_width);
89  else
90    return std::make_pair(left_growth_end - popup_width, popup_width);
91}
92
93std::pair<int,int> PopupControllerCommon::CalculatePopupYAndHeight(
94    const gfx::Display& top_display,
95    const gfx::Display& bottom_display,
96    int popup_required_height) const {
97  int topmost_display_y = top_display.bounds().y();
98  int bottommost_display_y =
99      bottom_display.GetSizeInPixel().height() + bottom_display.bounds().y();
100
101  // Calculate the start coordinates for the popup if it is growing down or
102  // the end position if it is growing up, capped to screen space.
103  int top_growth_end = std::max(topmost_display_y,
104                                std::min(bottommost_display_y,
105                                         RoundedElementBounds().y()));
106  int bottom_growth_start = std::max(topmost_display_y,
107                                     std::min(bottommost_display_y,
108                                              RoundedElementBounds().bottom()));
109
110  int top_available = bottom_growth_start - topmost_display_y;
111  int bottom_available = bottommost_display_y - top_growth_end;
112
113  // TODO(csharp): Restrict the popup height to what is available.
114  if (bottom_available >= popup_required_height ||
115      bottom_available >= top_available) {
116    // The popup can appear below the field.
117    return std::make_pair(bottom_growth_start, popup_required_height);
118  } else {
119    // The popup must appear above the field.
120    return std::make_pair(top_growth_end - popup_required_height,
121                          popup_required_height);
122  }
123}
124
125gfx::Rect PopupControllerCommon::GetPopupBounds(int desired_width,
126                                                int desired_height) const {
127  // This is the top left point of the popup if the popup is above the element
128  // and grows to the left (since that is the highest and furthest left the
129  // popup go could).
130  gfx::Point top_left_corner_of_popup = RoundedElementBounds().origin() +
131      gfx::Vector2d(RoundedElementBounds().width() - desired_width,
132                    -desired_height);
133
134  // This is the bottom right point of the popup if the popup is below the
135  // element and grows to the right (since the is the lowest and furthest right
136  // the popup could go).
137  gfx::Point bottom_right_corner_of_popup = RoundedElementBounds().origin() +
138      gfx::Vector2d(desired_width,
139                    RoundedElementBounds().height() + desired_height);
140
141  gfx::Display top_left_display = GetDisplayNearestPoint(
142      top_left_corner_of_popup);
143  gfx::Display bottom_right_display = GetDisplayNearestPoint(
144      bottom_right_corner_of_popup);
145
146  std::pair<int, int> popup_x_and_width =
147      CalculatePopupXAndWidth(top_left_display,
148                              bottom_right_display,
149                              desired_width);
150  std::pair<int, int> popup_y_and_height =
151      CalculatePopupYAndHeight(top_left_display,
152                               bottom_right_display,
153                               desired_height);
154
155  return gfx::Rect(popup_x_and_width.first,
156                   popup_y_and_height.first,
157                   popup_x_and_width.second,
158                   popup_y_and_height.second);
159}
160
161}  // namespace autofill
162