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