autofill_popup_view_gtk.cc revision 58537e28ecd584eab876aee8be7156509866d23a
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/gtk/autofill/autofill_popup_view_gtk.h"
6
7#include <gdk/gdkkeysyms.h>
8
9#include "base/logging.h"
10#include "base/strings/utf_string_conversions.h"
11#include "chrome/browser/ui/autofill/autofill_popup_controller.h"
12#include "chrome/browser/ui/gtk/gtk_util.h"
13#include "grit/ui_resources.h"
14#include "third_party/WebKit/public/web/WebAutofillClient.h"
15#include "ui/base/gtk/gtk_hig_constants.h"
16#include "ui/base/gtk/gtk_windowing.h"
17#include "ui/base/resource/resource_bundle.h"
18#include "ui/gfx/gtk_compat.h"
19#include "ui/gfx/image/image.h"
20#include "ui/gfx/native_widget_types.h"
21#include "ui/gfx/pango_util.h"
22#include "ui/gfx/rect.h"
23
24using WebKit::WebAutofillClient;
25
26namespace {
27
28const GdkColor kBorderColor = GDK_COLOR_RGB(0xc7, 0xca, 0xce);
29const GdkColor kHoveredBackgroundColor = GDK_COLOR_RGB(0xcd, 0xcd, 0xcd);
30const GdkColor kNameColor = GDK_COLOR_RGB(0x00, 0x00, 0x00);
31const GdkColor kWarningColor = GDK_COLOR_RGB(0x7f, 0x7f, 0x7f);
32const GdkColor kSubtextColor = GDK_COLOR_RGB(0x7f, 0x7f, 0x7f);
33
34}  // namespace
35
36namespace autofill {
37
38AutofillPopupViewGtk::AutofillPopupViewGtk(
39    AutofillPopupController* controller)
40    : controller_(controller),
41      window_(gtk_window_new(GTK_WINDOW_POPUP)) {
42  gtk_window_set_resizable(GTK_WINDOW(window_), FALSE);
43  gtk_widget_set_app_paintable(window_, TRUE);
44  gtk_widget_set_double_buffered(window_, TRUE);
45
46  // Setup the window to ensure it receives the expose event.
47  gtk_widget_add_events(window_, GDK_BUTTON_MOTION_MASK |
48                                 GDK_BUTTON_RELEASE_MASK |
49                                 GDK_EXPOSURE_MASK |
50                                 GDK_POINTER_MOTION_MASK);
51
52  GtkWidget* toplevel_window = gtk_widget_get_toplevel(
53      controller->container_view());
54  signals_.Connect(toplevel_window, "configure-event",
55                   G_CALLBACK(HandleConfigureThunk), this);
56  g_signal_connect(window_, "expose-event",
57                   G_CALLBACK(HandleExposeThunk), this);
58  g_signal_connect(window_, "leave-notify-event",
59                   G_CALLBACK(HandleLeaveThunk), this);
60  g_signal_connect(window_, "motion-notify-event",
61                   G_CALLBACK(HandleMotionThunk), this);
62  g_signal_connect(window_, "button-release-event",
63                   G_CALLBACK(HandleButtonReleaseThunk), this);
64
65  // Cache the layout so we don't have to create it for every expose.
66  layout_ = gtk_widget_create_pango_layout(window_, NULL);
67}
68
69AutofillPopupViewGtk::~AutofillPopupViewGtk() {
70  g_object_unref(layout_);
71  gtk_widget_destroy(window_);
72}
73
74void AutofillPopupViewGtk::Hide() {
75  delete this;
76}
77
78void AutofillPopupViewGtk::Show() {
79  UpdateBoundsAndRedrawPopup();
80
81  gtk_widget_show(window_);
82
83  GtkWidget* parent_window =
84      gtk_widget_get_toplevel(controller_->container_view());
85  ui::StackPopupWindow(window_, parent_window);
86}
87
88void AutofillPopupViewGtk::InvalidateRow(size_t row) {
89  GdkRectangle row_rect = controller_->GetRowBounds(row).ToGdkRectangle();
90  GdkWindow* gdk_window = gtk_widget_get_window(window_);
91  gdk_window_invalidate_rect(gdk_window, &row_rect, FALSE);
92}
93
94void AutofillPopupViewGtk::UpdateBoundsAndRedrawPopup() {
95  gtk_widget_set_size_request(window_,
96                              controller_->popup_bounds().width(),
97                              controller_->popup_bounds().height());
98  gtk_window_move(GTK_WINDOW(window_),
99                  controller_->popup_bounds().x(),
100                  controller_->popup_bounds().y());
101
102  GdkWindow* gdk_window = gtk_widget_get_window(window_);
103  GdkRectangle popup_rect = controller_->popup_bounds().ToGdkRectangle();
104  if (gdk_window != NULL)
105    gdk_window_invalidate_rect(gdk_window, &popup_rect, FALSE);
106}
107
108gboolean AutofillPopupViewGtk::HandleConfigure(GtkWidget* widget,
109                                               GdkEventConfigure* event) {
110  controller_->Hide();
111  return FALSE;
112}
113
114gboolean AutofillPopupViewGtk::HandleButtonRelease(GtkWidget* widget,
115                                                   GdkEventButton* event) {
116  // We only care about the left click.
117  if (event->button != 1)
118    return FALSE;
119
120  controller_->MouseClicked(event->x, event->y);
121  return TRUE;
122}
123
124gboolean AutofillPopupViewGtk::HandleExpose(GtkWidget* widget,
125                                            GdkEventExpose* event) {
126  cairo_t* cr = gdk_cairo_create(GDK_DRAWABLE(gtk_widget_get_window(widget)));
127  gdk_cairo_rectangle(cr, &event->area);
128  cairo_clip(cr);
129
130  // This assert is kinda ugly, but it would be more currently unneeded work
131  // to support painting a border that isn't 1 pixel thick.  There is no point
132  // in writing that code now, and explode if that day ever comes.
133  DCHECK_EQ(1, kBorderThickness);
134  // Draw the 1px border around the entire window.
135  gdk_cairo_set_source_color(cr, &kBorderColor);
136  gdk_cairo_rectangle(cr, &widget->allocation);
137  cairo_stroke(cr);
138  SetUpLayout();
139
140  gfx::Rect damage_rect(event->area);
141
142  for (size_t i = 0; i < controller_->names().size(); ++i) {
143    gfx::Rect line_rect = controller_->GetRowBounds(i);
144    // Only repaint and layout damaged lines.
145    if (!line_rect.Intersects(damage_rect))
146      continue;
147
148    if (controller_->identifiers()[i] == WebAutofillClient::MenuItemIDSeparator)
149      DrawSeparator(cr, line_rect);
150    else
151      DrawAutofillEntry(cr, i, line_rect);
152  }
153
154  cairo_destroy(cr);
155
156  return TRUE;
157}
158
159gboolean AutofillPopupViewGtk::HandleLeave(GtkWidget* widget,
160                                           GdkEventCrossing* event) {
161  controller_->MouseExitedPopup();
162
163  return FALSE;
164}
165
166gboolean AutofillPopupViewGtk::HandleMotion(GtkWidget* widget,
167                                            GdkEventMotion* event) {
168  controller_->MouseHovered(event->x, event->y);
169
170  return TRUE;
171}
172
173void AutofillPopupViewGtk::SetUpLayout() {
174  pango_layout_set_width(layout_, window_->allocation.width * PANGO_SCALE);
175  pango_layout_set_height(layout_, window_->allocation.height * PANGO_SCALE);
176}
177
178void AutofillPopupViewGtk::SetLayoutText(const string16& text,
179                                         const gfx::Font& font,
180                                         const GdkColor text_color) {
181  PangoAttrList* attrs = pango_attr_list_new();
182
183  PangoAttribute* fg_attr = pango_attr_foreground_new(text_color.red,
184                                                      text_color.green,
185                                                      text_color.blue);
186  pango_attr_list_insert(attrs, fg_attr);  // Ownership taken.
187
188  pango_layout_set_attributes(layout_, attrs);  // Ref taken.
189  pango_attr_list_unref(attrs);
190
191  gfx::ScopedPangoFontDescription font_description(font.GetNativeFont());
192  pango_layout_set_font_description(layout_, font_description.get());
193
194  gtk_util::SetLayoutText(layout_, text);
195
196  // The popup is already the correct size for the text, so set the width to -1
197  // to prevent additional wrapping or ellipsization.
198  pango_layout_set_width(layout_, -1);
199}
200
201void AutofillPopupViewGtk::DrawSeparator(cairo_t* cairo_context,
202                                         const gfx::Rect& separator_rect) {
203  cairo_save(cairo_context);
204  cairo_move_to(cairo_context, 0, separator_rect.y());
205  cairo_line_to(cairo_context,
206                separator_rect.width(),
207                separator_rect.y() + separator_rect.height());
208  cairo_stroke(cairo_context);
209  cairo_restore(cairo_context);
210}
211
212void AutofillPopupViewGtk::DrawAutofillEntry(cairo_t* cairo_context,
213                                             size_t index,
214                                             const gfx::Rect& entry_rect) {
215  if (controller_->selected_line() == static_cast<int>(index)) {
216    gdk_cairo_set_source_color(cairo_context, &kHoveredBackgroundColor);
217    cairo_rectangle(cairo_context, entry_rect.x(), entry_rect.y(),
218                    entry_rect.width(), entry_rect.height());
219    cairo_fill(cairo_context);
220  }
221
222  // Draw the value.
223  SetLayoutText(controller_->names()[index],
224                controller_->GetNameFontForRow(index),
225                controller_->IsWarning(index) ? kWarningColor : kNameColor);
226  int value_text_width = controller_->GetNameFontForRow(index).GetStringWidth(
227      controller_->names()[index]);
228
229  // Center the text within the line.
230  int row_height = entry_rect.height();
231  int value_content_y = std::max(
232      entry_rect.y(),
233      entry_rect.y() +
234          (row_height - controller_->GetNameFontForRow(index).GetHeight()) / 2);
235
236  bool is_rtl = controller_->IsRTL();
237  int value_content_x = is_rtl ?
238      entry_rect.width() - value_text_width - kEndPadding : kEndPadding;
239
240  cairo_save(cairo_context);
241  cairo_move_to(cairo_context, value_content_x, value_content_y);
242  pango_cairo_show_layout(cairo_context, layout_);
243  cairo_restore(cairo_context);
244
245  // Use this to figure out where all the other Autofill items should be placed.
246  int x_align_left = is_rtl ? kEndPadding : entry_rect.width() - kEndPadding;
247
248  // Draw the Autofill icon, if one exists
249  if (!controller_->icons()[index].empty()) {
250    int icon = controller_->GetIconResourceID(controller_->icons()[index]);
251    DCHECK_NE(-1, icon);
252    int icon_y = entry_rect.y() + (row_height - kAutofillIconHeight) / 2;
253
254    x_align_left += is_rtl ? 0 : -kAutofillIconWidth;
255
256    cairo_save(cairo_context);
257    ui::ResourceBundle& rb = ui::ResourceBundle::GetSharedInstance();
258    gtk_util::DrawFullImage(cairo_context,
259                            window_,
260                            rb.GetImageNamed(icon),
261                            x_align_left,
262                            icon_y);
263    cairo_restore(cairo_context);
264
265    x_align_left += is_rtl ? kAutofillIconWidth + kIconPadding : -kIconPadding;
266  }
267
268  // Draw the subtext.
269  SetLayoutText(controller_->subtexts()[index],
270                controller_->subtext_font(),
271                kSubtextColor);
272  if (!is_rtl) {
273    x_align_left -= controller_->subtext_font().GetStringWidth(
274        controller_->subtexts()[index]);
275  }
276
277  // Center the text within the line.
278  int subtext_content_y = std::max(
279      entry_rect.y(),
280      entry_rect.y() +
281          (row_height - controller_->subtext_font().GetHeight()) / 2);
282
283  cairo_save(cairo_context);
284  cairo_move_to(cairo_context, x_align_left, subtext_content_y);
285  pango_cairo_show_layout(cairo_context, layout_);
286  cairo_restore(cairo_context);
287}
288
289AutofillPopupView* AutofillPopupView::Create(
290    AutofillPopupController* controller) {
291  return new AutofillPopupViewGtk(controller);
292}
293
294}  // namespace autofill
295