autofill_popup_view_views.cc revision 0f1bc08d4cfcc34181b0b5cbf065c40f687bf740
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 WebKit::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_->MouseExitedPopup();
93}
94
95bool AutofillPopupViewViews::OnMouseDragged(const ui::MouseEvent& event) {
96  if (!controller_)
97    return false;
98
99  if (HitTestPoint(event.location())) {
100    controller_->MouseHovered(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_->MouseExitedPopup();
109  return false;
110}
111
112void AutofillPopupViewViews::OnMouseExited(const ui::MouseEvent& event) {
113  if (controller_)
114    controller_->MouseExitedPopup();
115}
116
117void AutofillPopupViewViews::OnMouseMoved(const ui::MouseEvent& event) {
118  if (!controller_)
119    return;
120
121  if (HitTestPoint(event.location()))
122    controller_->MouseHovered(event.x(), event.y());
123  else
124    controller_->MouseExitedPopup();
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_->MouseClicked(event.x(), event.y());
167}
168
169void AutofillPopupViewViews::OnWidgetBoundsChanged(
170    views::Widget* widget,
171    const gfx::Rect& new_bounds) {
172  DCHECK_EQ(widget, observing_widget_);
173  controller_->Hide();
174}
175
176void AutofillPopupViewViews::Show() {
177  if (!GetWidget()) {
178    observing_widget_->AddObserver(this);
179
180    // The widget is destroyed by the corresponding NativeWidget, so we use
181    // a weak pointer to hold the reference and don't have to worry about
182    // deletion.
183    views::Widget* widget = new views::Widget;
184    views::Widget::InitParams params(views::Widget::InitParams::TYPE_POPUP);
185    params.delegate = this;
186    params.parent = controller_->container_view();
187    widget->Init(params);
188    widget->SetContentsView(this);
189#if defined(USE_AURA)
190    // No animation for popup appearance (too distracting).
191    views::corewm::SetWindowVisibilityAnimationTransition(
192        widget->GetNativeView(), views::corewm::ANIMATE_HIDE);
193#endif
194  }
195
196  set_border(views::Border::CreateSolidBorder(kBorderThickness, kBorderColor));
197
198  UpdateBoundsAndRedrawPopup();
199  GetWidget()->Show();
200
201  if (controller_->hide_on_outside_click())
202    GetWidget()->SetCapture(this);
203}
204
205void AutofillPopupViewViews::InvalidateRow(size_t row) {
206  SchedulePaintInRect(controller_->GetRowBounds(row));
207}
208
209void AutofillPopupViewViews::UpdateBoundsAndRedrawPopup() {
210  GetWidget()->SetBounds(controller_->popup_bounds());
211  SchedulePaint();
212}
213
214void AutofillPopupViewViews::HideInternal() {
215  observing_widget_->RemoveObserver(this);
216}
217
218void AutofillPopupViewViews::DrawAutofillEntry(gfx::Canvas* canvas,
219                                               int index,
220                                               const gfx::Rect& entry_rect) {
221  if (controller_->selected_line() == index)
222    canvas->FillRect(entry_rect, kHoveredBackgroundColor);
223
224  bool is_rtl = controller_->IsRTL();
225  int value_text_width = controller_->GetNameFontForRow(index).GetStringWidth(
226      controller_->names()[index]);
227  int value_content_x = is_rtl ?
228      entry_rect.width() - value_text_width - kEndPadding : kEndPadding;
229
230  canvas->DrawStringInt(
231      controller_->names()[index],
232      controller_->GetNameFontForRow(index),
233      controller_->IsWarning(index) ? kWarningTextColor : kValueTextColor,
234      value_content_x,
235      entry_rect.y(),
236      canvas->GetStringWidth(controller_->names()[index],
237                             controller_->GetNameFontForRow(index)),
238      entry_rect.height(),
239      gfx::Canvas::TEXT_ALIGN_CENTER);
240
241  // Use this to figure out where all the other Autofill items should be placed.
242  int x_align_left = is_rtl ? kEndPadding : entry_rect.width() - kEndPadding;
243
244  // Draw the Autofill icon, if one exists
245  ui::ResourceBundle& rb = ui::ResourceBundle::GetSharedInstance();
246  int row_height = controller_->GetRowBounds(index).height();
247  if (!controller_->icons()[index].empty()) {
248    int icon = controller_->GetIconResourceID(controller_->icons()[index]);
249    DCHECK_NE(-1, icon);
250    const gfx::ImageSkia* image = rb.GetImageSkiaNamed(icon);
251    int icon_y = entry_rect.y() + (row_height - image->height()) / 2;
252
253    x_align_left += is_rtl ? 0 : -image->width();
254
255    canvas->DrawImageInt(*image, x_align_left, icon_y);
256
257    x_align_left += is_rtl ? image->width() + kIconPadding : -kIconPadding;
258  }
259
260  // Draw the name text.
261  if (!is_rtl) {
262    x_align_left -= canvas->GetStringWidth(controller_->subtexts()[index],
263                                           controller_->subtext_font());
264  }
265
266  canvas->DrawStringInt(
267      controller_->subtexts()[index],
268      controller_->subtext_font(),
269      kItemTextColor,
270      x_align_left,
271      entry_rect.y(),
272      canvas->GetStringWidth(controller_->subtexts()[index],
273                             controller_->subtext_font()),
274      entry_rect.height(),
275      gfx::Canvas::TEXT_ALIGN_CENTER);
276}
277
278AutofillPopupView* AutofillPopupView::Create(
279    AutofillPopupController* controller) {
280  views::Widget* observing_widget =
281      views::Widget::GetTopLevelWidgetForNativeView(
282          controller->container_view());
283
284  // If the top level widget can't be found, cancel the popup since we can't
285  // fully set it up.
286  if (!observing_widget)
287    return NULL;
288
289  return new AutofillPopupViewViews(controller, observing_widget);
290}
291
292}  // namespace autofill
293