autofill_popup_view_gtk.cc revision 7d4cd473f85ac64c3747c96c277f9e506a0d2246
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_compat.h"
16#include "ui/base/gtk/gtk_hig_constants.h"
17#include "ui/base/gtk/gtk_windowing.h"
18#include "ui/base/resource/resource_bundle.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  AutofillPopupView::Hide();
76
77  delete this;
78}
79
80void AutofillPopupViewGtk::Show() {
81  UpdateBoundsAndRedrawPopup();
82
83  gtk_widget_show(window_);
84
85  GtkWidget* parent_window =
86      gtk_widget_get_toplevel(controller_->container_view());
87  ui::StackPopupWindow(window_, parent_window);
88}
89
90void AutofillPopupViewGtk::InvalidateRow(size_t row) {
91  GdkRectangle row_rect = controller_->GetRowBounds(row).ToGdkRectangle();
92  GdkWindow* gdk_window = gtk_widget_get_window(window_);
93  gdk_window_invalidate_rect(gdk_window, &row_rect, FALSE);
94}
95
96void AutofillPopupViewGtk::UpdateBoundsAndRedrawPopup() {
97  gtk_widget_set_size_request(window_,
98                              controller_->popup_bounds().width(),
99                              controller_->popup_bounds().height());
100  gtk_window_move(GTK_WINDOW(window_),
101                  controller_->popup_bounds().x(),
102                  controller_->popup_bounds().y());
103
104  GdkWindow* gdk_window = gtk_widget_get_window(window_);
105  GdkRectangle popup_rect = controller_->popup_bounds().ToGdkRectangle();
106  if (gdk_window != NULL)
107    gdk_window_invalidate_rect(gdk_window, &popup_rect, FALSE);
108}
109
110gboolean AutofillPopupViewGtk::HandleConfigure(GtkWidget* widget,
111                                               GdkEventConfigure* event) {
112  controller_->Hide();
113  return FALSE;
114}
115
116gboolean AutofillPopupViewGtk::HandleButtonRelease(GtkWidget* widget,
117                                                   GdkEventButton* event) {
118  // We only care about the left click.
119  if (event->button != 1)
120    return FALSE;
121
122  controller_->MouseClicked(event->x, event->y);
123  return TRUE;
124}
125
126gboolean AutofillPopupViewGtk::HandleExpose(GtkWidget* widget,
127                                            GdkEventExpose* event) {
128  cairo_t* cr = gdk_cairo_create(GDK_DRAWABLE(gtk_widget_get_window(widget)));
129  gdk_cairo_rectangle(cr, &event->area);
130  cairo_clip(cr);
131
132  // This assert is kinda ugly, but it would be more currently unneeded work
133  // to support painting a border that isn't 1 pixel thick.  There is no point
134  // in writing that code now, and explode if that day ever comes.
135  DCHECK_EQ(1, kBorderThickness);
136  // Draw the 1px border around the entire window.
137  gdk_cairo_set_source_color(cr, &kBorderColor);
138  gdk_cairo_rectangle(cr, &widget->allocation);
139  cairo_stroke(cr);
140  SetUpLayout();
141
142  gfx::Rect damage_rect(event->area);
143
144  for (size_t i = 0; i < controller_->names().size(); ++i) {
145    gfx::Rect line_rect = controller_->GetRowBounds(i);
146    // Only repaint and layout damaged lines.
147    if (!line_rect.Intersects(damage_rect))
148      continue;
149
150    if (controller_->identifiers()[i] == WebAutofillClient::MenuItemIDSeparator)
151      DrawSeparator(cr, line_rect);
152    else
153      DrawAutofillEntry(cr, i, line_rect);
154  }
155
156  cairo_destroy(cr);
157
158  return TRUE;
159}
160
161gboolean AutofillPopupViewGtk::HandleLeave(GtkWidget* widget,
162                                           GdkEventCrossing* event) {
163  controller_->MouseExitedPopup();
164
165  return FALSE;
166}
167
168gboolean AutofillPopupViewGtk::HandleMotion(GtkWidget* widget,
169                                            GdkEventMotion* event) {
170  controller_->MouseHovered(event->x, event->y);
171
172  return TRUE;
173}
174
175void AutofillPopupViewGtk::SetUpLayout() {
176  pango_layout_set_width(layout_, window_->allocation.width * PANGO_SCALE);
177  pango_layout_set_height(layout_, window_->allocation.height * PANGO_SCALE);
178}
179
180void AutofillPopupViewGtk::SetLayoutText(const string16& text,
181                                         const gfx::Font& font,
182                                         const GdkColor text_color) {
183  PangoAttrList* attrs = pango_attr_list_new();
184
185  PangoAttribute* fg_attr = pango_attr_foreground_new(text_color.red,
186                                                      text_color.green,
187                                                      text_color.blue);
188  pango_attr_list_insert(attrs, fg_attr);  // Ownership taken.
189
190  pango_layout_set_attributes(layout_, attrs);  // Ref taken.
191  pango_attr_list_unref(attrs);
192
193  gfx::ScopedPangoFontDescription font_description(font.GetNativeFont());
194  pango_layout_set_font_description(layout_, font_description.get());
195
196  gtk_util::SetLayoutText(layout_, text);
197
198  // We add one pixel to the width because if the text fills up the width
199  // pango will try to split it over 2 lines.
200  int required_width = font.GetStringWidth(text) + 1;
201
202  pango_layout_set_width(layout_, required_width * PANGO_SCALE);
203}
204
205void AutofillPopupViewGtk::DrawSeparator(cairo_t* cairo_context,
206                                         const gfx::Rect& separator_rect) {
207  cairo_save(cairo_context);
208  cairo_move_to(cairo_context, 0, separator_rect.y());
209  cairo_line_to(cairo_context,
210                separator_rect.width(),
211                separator_rect.y() + separator_rect.height());
212  cairo_stroke(cairo_context);
213  cairo_restore(cairo_context);
214}
215
216void AutofillPopupViewGtk::DrawAutofillEntry(cairo_t* cairo_context,
217                                             size_t index,
218                                             const gfx::Rect& entry_rect) {
219  if (controller_->selected_line() == static_cast<int>(index)) {
220    gdk_cairo_set_source_color(cairo_context, &kHoveredBackgroundColor);
221    cairo_rectangle(cairo_context, entry_rect.x(), entry_rect.y(),
222                    entry_rect.width(), entry_rect.height());
223    cairo_fill(cairo_context);
224  }
225
226  // Draw the value.
227  SetLayoutText(controller_->names()[index],
228                controller_->GetNameFontForRow(index),
229                controller_->IsWarning(index) ? kWarningColor : kNameColor);
230  int value_text_width = controller_->GetNameFontForRow(index).GetStringWidth(
231      controller_->names()[index]);
232
233  // Center the text within the line.
234  int row_height = entry_rect.height();
235  int value_content_y = std::max(
236      entry_rect.y(),
237      entry_rect.y() +
238          (row_height - controller_->GetNameFontForRow(index).GetHeight()) / 2);
239
240  bool is_rtl = controller_->IsRTL();
241  int value_content_x = is_rtl ?
242      entry_rect.width() - value_text_width - kEndPadding : kEndPadding;
243
244  cairo_save(cairo_context);
245  cairo_move_to(cairo_context, value_content_x, value_content_y);
246  pango_cairo_show_layout(cairo_context, layout_);
247  cairo_restore(cairo_context);
248
249  // Use this to figure out where all the other Autofill items should be placed.
250  int x_align_left = is_rtl ? kEndPadding : entry_rect.width() - kEndPadding;
251
252  // Draw the Autofill icon, if one exists
253  if (!controller_->icons()[index].empty()) {
254    int icon = controller_->GetIconResourceID(controller_->icons()[index]);
255    DCHECK_NE(-1, icon);
256    int icon_y = entry_rect.y() + (row_height - kAutofillIconHeight) / 2;
257
258    x_align_left += is_rtl ? 0 : -kAutofillIconWidth;
259
260    cairo_save(cairo_context);
261    ui::ResourceBundle& rb = ui::ResourceBundle::GetSharedInstance();
262    gtk_util::DrawFullImage(cairo_context,
263                            window_,
264                            rb.GetImageNamed(icon),
265                            x_align_left,
266                            icon_y);
267    cairo_restore(cairo_context);
268
269    x_align_left += is_rtl ? kAutofillIconWidth + kIconPadding : -kIconPadding;
270  }
271
272  // Draw the subtext.
273  SetLayoutText(controller_->subtexts()[index],
274                controller_->subtext_font(),
275                kSubtextColor);
276  if (!is_rtl) {
277    x_align_left -= controller_->subtext_font().GetStringWidth(
278        controller_->subtexts()[index]);
279  }
280
281  // Center the text within the line.
282  int subtext_content_y = std::max(
283      entry_rect.y(),
284      entry_rect.y() +
285          (row_height - controller_->subtext_font().GetHeight()) / 2);
286
287  cairo_save(cairo_context);
288  cairo_move_to(cairo_context, x_align_left, subtext_content_y);
289  pango_cairo_show_layout(cairo_context, layout_);
290  cairo_restore(cairo_context);
291}
292
293AutofillPopupView* AutofillPopupView::Create(
294    AutofillPopupController* controller) {
295  return new AutofillPopupViewGtk(controller);
296}
297
298}  // namespace autofill
299