autofill_popup_view_views.cc revision d0247b1b59f9c528cb6df88b4f2b9afaf80d181e
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/views/autofill/autofill_popup_view_views.h"
6
7#include "chrome/browser/ui/autofill/autofill_popup_controller.h"
8#include "grit/ui_resources.h"
9#include "third_party/WebKit/public/web/WebAutofillClient.h"
10#include "ui/base/resource/resource_bundle.h"
11#include "ui/events/keycodes/keyboard_codes.h"
12#include "ui/gfx/canvas.h"
13#include "ui/gfx/image/image.h"
14#include "ui/gfx/native_widget_types.h"
15#include "ui/gfx/point.h"
16#include "ui/gfx/rect.h"
17#include "ui/gfx/screen.h"
18#include "ui/views/border.h"
19#include "ui/views/event_utils.h"
20#include "ui/views/widget/widget.h"
21
22using WebKit::WebAutofillClient;
23
24namespace {
25
26const SkColor kBorderColor = SkColorSetARGB(0xFF, 0xC7, 0xCA, 0xCE);
27const SkColor kHoveredBackgroundColor = SkColorSetARGB(0xFF, 0xCD, 0xCD, 0xCD);
28const SkColor kItemTextColor = SkColorSetARGB(0xFF, 0x7F, 0x7F, 0x7F);
29const SkColor kPopupBackground = SkColorSetARGB(0xFF, 0xFF, 0xFF, 0xFF);
30const SkColor kValueTextColor = SkColorSetARGB(0xFF, 0x00, 0x00, 0x00);
31const SkColor kWarningTextColor = SkColorSetARGB(0xFF, 0x7F, 0x7F, 0x7F);
32
33}  // namespace
34
35namespace autofill {
36
37AutofillPopupViewViews::AutofillPopupViewViews(
38    AutofillPopupController* controller, views::Widget* observing_widget)
39    : controller_(controller),
40      observing_widget_(observing_widget) {}
41
42AutofillPopupViewViews::~AutofillPopupViewViews() {
43  if (controller_) {
44    controller_->ViewDestroyed();
45
46    HideInternal();
47  }
48}
49
50void AutofillPopupViewViews::Hide() {
51  // The controller is no longer valid after it hides us.
52  controller_ = NULL;
53
54  HideInternal();
55
56  if (GetWidget()) {
57    // Don't call CloseNow() because some of the functions higher up the stack
58    // assume the the widget is still valid after this point.
59    // http://crbug.com/229224
60    // NOTE: This deletes |this|.
61    GetWidget()->Close();
62  } else {
63    delete this;
64  }
65}
66
67void AutofillPopupViewViews::OnPaint(gfx::Canvas* canvas) {
68  if (!controller_)
69    return;
70
71  canvas->DrawColor(kPopupBackground);
72  OnPaintBorder(canvas);
73
74  for (size_t i = 0; i < controller_->names().size(); ++i) {
75    gfx::Rect line_rect = controller_->GetRowBounds(i);
76
77    if (controller_->identifiers()[i] ==
78            WebAutofillClient::MenuItemIDSeparator) {
79      canvas->DrawRect(line_rect, kItemTextColor);
80    } else {
81      DrawAutofillEntry(canvas, i, line_rect);
82    }
83  }
84}
85
86void AutofillPopupViewViews::OnMouseCaptureLost() {
87  if (controller_)
88    controller_->MouseExitedPopup();
89}
90
91bool AutofillPopupViewViews::OnMouseDragged(const ui::MouseEvent& event) {
92  if (!controller_)
93    return false;
94
95  if (HitTestPoint(event.location())) {
96    controller_->MouseHovered(event.x(), event.y());
97
98    // We must return true in order to get future OnMouseDragged and
99    // OnMouseReleased events.
100    return true;
101  }
102
103  // If we move off of the popup, we lose the selection.
104  controller_->MouseExitedPopup();
105  return false;
106}
107
108void AutofillPopupViewViews::OnMouseExited(const ui::MouseEvent& event) {
109  if (controller_)
110    controller_->MouseExitedPopup();
111}
112
113void AutofillPopupViewViews::OnMouseMoved(const ui::MouseEvent& event) {
114  if (!controller_)
115    return;
116
117  if (HitTestPoint(event.location()))
118    controller_->MouseHovered(event.x(), event.y());
119  else
120    controller_->MouseExitedPopup();
121}
122
123bool AutofillPopupViewViews::OnMousePressed(const ui::MouseEvent& event) {
124  if (HitTestPoint(event.location()))
125    return true;
126
127  if (controller_->hide_on_outside_click()) {
128    GetWidget()->ReleaseCapture();
129
130    gfx::Point screen_loc = event.location();
131    views::View::ConvertPointToScreen(this, &screen_loc);
132
133    ui::MouseEvent mouse_event = event;
134    mouse_event.set_location(screen_loc);
135
136    if (controller_->ShouldRepostEvent(mouse_event)) {
137      gfx::NativeView native_view = GetWidget()->GetNativeView();
138      gfx::Screen* screen = gfx::Screen::GetScreenFor(native_view);
139      gfx::NativeWindow window = screen->GetWindowAtScreenPoint(screen_loc);
140      views::RepostLocatedEvent(window, mouse_event);
141    }
142
143    controller_->Hide();
144    // |this| is now deleted.
145  }
146
147  return false;
148}
149
150void AutofillPopupViewViews::OnMouseReleased(const ui::MouseEvent& event) {
151  if (!controller_)
152    return;
153
154  // Because this view can can be shown in response to a mouse press, it can
155  // receive an OnMouseReleased event just after showing. This breaks the mouse
156  // capture, so restart capturing here.
157  if (controller_->hide_on_outside_click() && GetWidget())
158    GetWidget()->SetCapture(this);
159
160  // We only care about the left click.
161  if (event.IsOnlyLeftMouseButton() && HitTestPoint(event.location()))
162    controller_->MouseClicked(event.x(), event.y());
163}
164
165void AutofillPopupViewViews::OnWidgetBoundsChanged(
166    views::Widget* widget,
167    const gfx::Rect& new_bounds) {
168  DCHECK_EQ(widget, observing_widget_);
169  controller_->Hide();
170}
171
172void AutofillPopupViewViews::Show() {
173  if (!GetWidget()) {
174    observing_widget_->AddObserver(this);
175
176    // The widget is destroyed by the corresponding NativeWidget, so we use
177    // a weak pointer to hold the reference and don't have to worry about
178    // deletion.
179    views::Widget* widget = new views::Widget;
180    views::Widget::InitParams params(views::Widget::InitParams::TYPE_POPUP);
181    params.delegate = this;
182    params.parent = controller_->container_view();
183    widget->Init(params);
184    widget->SetContentsView(this);
185  }
186
187  set_border(views::Border::CreateSolidBorder(kBorderThickness, kBorderColor));
188
189  UpdateBoundsAndRedrawPopup();
190  GetWidget()->Show();
191
192  if (controller_->hide_on_outside_click())
193    GetWidget()->SetCapture(this);
194}
195
196void AutofillPopupViewViews::InvalidateRow(size_t row) {
197  SchedulePaintInRect(controller_->GetRowBounds(row));
198}
199
200void AutofillPopupViewViews::UpdateBoundsAndRedrawPopup() {
201  GetWidget()->SetBounds(controller_->popup_bounds());
202  SchedulePaint();
203}
204
205void AutofillPopupViewViews::HideInternal() {
206  observing_widget_->RemoveObserver(this);
207}
208
209void AutofillPopupViewViews::DrawAutofillEntry(gfx::Canvas* canvas,
210                                               int index,
211                                               const gfx::Rect& entry_rect) {
212  if (controller_->selected_line() == index)
213    canvas->FillRect(entry_rect, kHoveredBackgroundColor);
214
215  bool is_rtl = controller_->IsRTL();
216  int value_text_width = controller_->GetNameFontForRow(index).GetStringWidth(
217      controller_->names()[index]);
218  int value_content_x = is_rtl ?
219      entry_rect.width() - value_text_width - kEndPadding : kEndPadding;
220
221  canvas->DrawStringInt(
222      controller_->names()[index],
223      controller_->GetNameFontForRow(index),
224      controller_->IsWarning(index) ? kWarningTextColor : kValueTextColor,
225      value_content_x,
226      entry_rect.y(),
227      canvas->GetStringWidth(controller_->names()[index],
228                             controller_->GetNameFontForRow(index)),
229      entry_rect.height(),
230      gfx::Canvas::TEXT_ALIGN_CENTER);
231
232  // Use this to figure out where all the other Autofill items should be placed.
233  int x_align_left = is_rtl ? kEndPadding : entry_rect.width() - kEndPadding;
234
235  // Draw the Autofill icon, if one exists
236  ui::ResourceBundle& rb = ui::ResourceBundle::GetSharedInstance();
237  int row_height = controller_->GetRowBounds(index).height();
238  if (!controller_->icons()[index].empty()) {
239    int icon = controller_->GetIconResourceID(controller_->icons()[index]);
240    DCHECK_NE(-1, icon);
241    int icon_y = entry_rect.y() + (row_height - kAutofillIconHeight) / 2;
242
243    x_align_left += is_rtl ? 0 : -kAutofillIconWidth;
244
245    canvas->DrawImageInt(*rb.GetImageSkiaNamed(icon), x_align_left, icon_y);
246
247    x_align_left += is_rtl ? kAutofillIconWidth + kIconPadding : -kIconPadding;
248  }
249
250  // Draw the name text.
251  if (!is_rtl) {
252    x_align_left -= canvas->GetStringWidth(controller_->subtexts()[index],
253                                           controller_->subtext_font());
254  }
255
256  canvas->DrawStringInt(
257      controller_->subtexts()[index],
258      controller_->subtext_font(),
259      kItemTextColor,
260      x_align_left,
261      entry_rect.y(),
262      canvas->GetStringWidth(controller_->subtexts()[index],
263                             controller_->subtext_font()),
264      entry_rect.height(),
265      gfx::Canvas::TEXT_ALIGN_CENTER);
266}
267
268AutofillPopupView* AutofillPopupView::Create(
269    AutofillPopupController* controller) {
270  views::Widget* observing_widget =
271      views::Widget::GetTopLevelWidgetForNativeView(
272          controller->container_view());
273
274  // If the top level widget can't be found, cancel the popup since we can't
275  // fully set it up.
276  if (!observing_widget)
277    return NULL;
278
279  return new AutofillPopupViewViews(controller, observing_widget);
280}
281
282}  // namespace autofill
283