Share Font Code for New Autofill.
[chromium-blink-merge.git] / chrome / browser / ui / gtk / autofill / autofill_popup_view_gtk.cc
blobe7a13e032315d1427008449c83c0582cdaa4214f
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.
5 #include "autofill_popup_view_gtk.h"
7 #include <gdk/gdkkeysyms.h>
9 #include "base/i18n/rtl.h"
10 #include "base/logging.h"
11 #include "base/utf_string_conversions.h"
12 #include "chrome/browser/autofill/autofill_external_delegate.h"
13 #include "chrome/browser/ui/gtk/gtk_util.h"
14 #include "chrome/browser/ui/gtk/gtk_theme_service.h"
15 #include "content/public/browser/render_view_host.h"
16 #include "content/public/browser/web_contents.h"
17 #include "grit/ui_resources.h"
18 #include "third_party/WebKit/Source/WebKit/chromium/public/WebAutofillClient.h"
19 #include "ui/base/gtk/gtk_compat.h"
20 #include "ui/base/gtk/gtk_hig_constants.h"
21 #include "ui/base/gtk/gtk_windowing.h"
22 #include "ui/gfx/native_widget_types.h"
23 #include "ui/gfx/pango_util.h"
24 #include "ui/gfx/rect.h"
26 using WebKit::WebAutofillClient;
28 namespace {
29 const GdkColor kBorderColor = GDK_COLOR_RGB(0xc7, 0xca, 0xce);
30 const GdkColor kHoveredBackgroundColor = GDK_COLOR_RGB(0xcd, 0xcd, 0xcd);
31 const GdkColor kValueTextColor = GDK_COLOR_RGB(0x00, 0x00, 0x00);
32 const GdkColor kLabelTextColor = GDK_COLOR_RGB(0x7f, 0x7f, 0x7f);
34 // The vertical height of each row in pixels.
35 const int kRowHeight = 24;
37 // The vertical height of a separator in pixels.
38 const int kSeparatorHeight = 1;
40 // The amount of minimum padding between the Autofill value and label in pixels.
41 const int kLabelPadding = 15;
43 // The amount of padding between icons in pixels.
44 const int kIconPadding = 5;
46 // The amount of padding at the end of the popup in pixels.
47 const int kEndPadding = 3;
49 // We have a 1 pixel border around the entire results popup.
50 const int kBorderThickness = 1;
52 // Width of the Autofill icons in pixels.
53 const int kAutofillIconWidth = 25;
55 // Height of the Autofill icons in pixels.
56 const int kAutofillIconHeight = 16;
58 // Width of the delete icon in pixels.
59 const int kDeleteIconWidth = 16;
61 // Height of the delete icon in pixels.
62 const int kDeleteIconHeight = 16;
64 gfx::Rect GetWindowRect(GdkWindow* window) {
65 return gfx::Rect(gdk_window_get_width(window),
66 gdk_window_get_height(window));
69 int GetRowHeight(int unique_id) {
70 if (unique_id == WebAutofillClient::MenuItemIDSeparator)
71 return kSeparatorHeight;
73 return kRowHeight;
76 } // namespace
78 AutofillPopupViewGtk::AutofillPopupViewGtk(
79 content::WebContents* web_contents,
80 GtkThemeService* theme_service,
81 AutofillExternalDelegate* external_delegate,
82 GtkWidget* parent)
83 : AutofillPopupView(web_contents, external_delegate),
84 parent_(parent),
85 window_(gtk_window_new(GTK_WINDOW_POPUP)),
86 theme_service_(theme_service),
87 render_view_host_(web_contents->GetRenderViewHost()),
88 delete_icon_selected_(false) {
89 CHECK(parent != NULL);
90 gtk_window_set_resizable(GTK_WINDOW(window_), FALSE);
91 gtk_widget_set_app_paintable(window_, TRUE);
92 gtk_widget_set_double_buffered(window_, TRUE);
94 // Setup the window to ensure it receives the expose event.
95 gtk_widget_add_events(window_, GDK_BUTTON_MOTION_MASK |
96 GDK_BUTTON_RELEASE_MASK |
97 GDK_EXPOSURE_MASK |
98 GDK_POINTER_MOTION_MASK);
99 g_signal_connect(window_, "expose-event",
100 G_CALLBACK(HandleExposeThunk), this);
101 g_signal_connect(window_, "leave-notify-event",
102 G_CALLBACK(HandleLeaveThunk), this);
103 g_signal_connect(window_, "motion-notify-event",
104 G_CALLBACK(HandleMotionThunk), this);
105 g_signal_connect(window_, "button-release-event",
106 G_CALLBACK(HandleButtonReleaseThunk), this);
108 // Cache the layout so we don't have to create it for every expose.
109 layout_ = gtk_widget_create_pango_layout(window_, NULL);
112 AutofillPopupViewGtk::~AutofillPopupViewGtk() {
113 g_object_unref(layout_);
114 gtk_widget_destroy(window_);
117 void AutofillPopupViewGtk::ShowInternal() {
118 SetBounds();
119 gtk_window_move(GTK_WINDOW(window_), bounds_.x(), bounds_.y());
121 ResizePopup();
123 render_view_host_->AddKeyboardListener(this);
125 gtk_widget_show(window_);
127 GtkWidget* toplevel = gtk_widget_get_toplevel(parent_);
128 CHECK(gtk_widget_is_toplevel(toplevel));
129 ui::StackPopupWindow(window_, toplevel);
132 void AutofillPopupViewGtk::HideInternal() {
133 render_view_host_->RemoveKeyboardListener(this);
135 gtk_widget_hide(window_);
138 void AutofillPopupViewGtk::InvalidateRow(size_t row) {
139 GdkRectangle row_rect = GetRectForRow(row, bounds_.width()).ToGdkRectangle();
140 GdkWindow* gdk_window = gtk_widget_get_window(window_);
141 gdk_window_invalidate_rect(gdk_window, &row_rect, FALSE);
144 void AutofillPopupViewGtk::ResizePopup() {
145 bounds_.set_width(GetPopupRequiredWidth());
146 bounds_.set_height(GetPopupRequiredHeight());
148 gtk_widget_set_size_request(window_, bounds_.width(), bounds_.height());
151 gboolean AutofillPopupViewGtk::HandleButtonRelease(GtkWidget* widget,
152 GdkEventButton* event) {
153 // We only care about the left click.
154 if (event->button != 1)
155 return FALSE;
157 DCHECK_EQ(selected_line(), LineFromY(event->y));
159 if (DeleteIconIsSelected(event->x, event->y))
160 RemoveSelectedLine();
161 else
162 AcceptSelectedLine();
164 return TRUE;
167 gboolean AutofillPopupViewGtk::HandleExpose(GtkWidget* widget,
168 GdkEventExpose* event) {
169 gfx::Rect window_rect = GetWindowRect(event->window);
170 gfx::Rect damage_rect = gfx::Rect(event->area);
172 cairo_t* cr = gdk_cairo_create(GDK_DRAWABLE(gtk_widget_get_window(widget)));
173 gdk_cairo_rectangle(cr, &event->area);
174 cairo_clip(cr);
176 // This assert is kinda ugly, but it would be more currently unneeded work
177 // to support painting a border that isn't 1 pixel thick. There is no point
178 // in writing that code now, and explode if that day ever comes.
179 COMPILE_ASSERT(kBorderThickness == 1, border_1px_implied);
180 // Draw the 1px border around the entire window.
181 gdk_cairo_set_source_color(cr, &kBorderColor);
182 cairo_rectangle(cr, 0, 0, window_rect.width(), window_rect.height());
183 cairo_stroke(cr);
185 SetupLayout(window_rect);
187 for (size_t i = 0; i < autofill_values().size(); ++i) {
188 gfx::Rect line_rect = GetRectForRow(i, window_rect.width());
189 // Only repaint and layout damaged lines.
190 if (!line_rect.Intersects(damage_rect))
191 continue;
193 if (autofill_unique_ids()[i] == WebAutofillClient::MenuItemIDSeparator)
194 DrawSeparator(cr, line_rect);
195 else
196 DrawAutofillEntry(cr, i, line_rect);
199 cairo_destroy(cr);
201 return TRUE;
204 gboolean AutofillPopupViewGtk::HandleLeave(GtkWidget* widget,
205 GdkEventCrossing* event) {
206 ClearSelectedLine();
208 return FALSE;
211 gboolean AutofillPopupViewGtk::HandleMotion(GtkWidget* widget,
212 GdkEventMotion* event) {
213 // TODO(csharp): Only select a line if the motion is still inside the popup.
214 // http://www.crbug.com/129559
215 int line = LineFromY(event->y);
217 SetSelectedLine(line);
219 bool delete_icon_selected = DeleteIconIsSelected(event->x, event->y);
220 if (delete_icon_selected != delete_icon_selected_) {
221 delete_icon_selected_ = delete_icon_selected;
222 InvalidateRow(selected_line());
225 return TRUE;
228 bool AutofillPopupViewGtk::HandleKeyPressEvent(GdkEventKey* event) {
229 // Filter modifier to only include accelerator modifiers.
230 guint modifier = event->state & gtk_accelerator_get_default_mod_mask();
232 switch (event->keyval) {
233 case GDK_Up:
234 SelectPreviousLine();
235 return true;
236 case GDK_Down:
237 SelectNextLine();
238 return true;
239 case GDK_Page_Up:
240 SetSelectedLine(0);
241 return true;
242 case GDK_Page_Down:
243 SetSelectedLine(autofill_values().size() - 1);
244 return true;
245 case GDK_Escape:
246 Hide();
247 return true;
248 case GDK_Delete:
249 case GDK_KP_Delete:
250 return (modifier == GDK_SHIFT_MASK) && RemoveSelectedLine();
251 case GDK_Return:
252 case GDK_KP_Enter:
253 return AcceptSelectedLine();
256 return false;
259 void AutofillPopupViewGtk::SetupLayout(const gfx::Rect& window_rect) {
260 int allocated_content_width = window_rect.width();
261 pango_layout_set_width(layout_, allocated_content_width * PANGO_SCALE);
262 pango_layout_set_height(layout_, kRowHeight * PANGO_SCALE);
265 void AutofillPopupViewGtk::SetLayoutText(const string16& text,
266 const gfx::Font& font,
267 const GdkColor text_color) {
268 PangoAttrList* attrs = pango_attr_list_new();
270 PangoAttribute* fg_attr = pango_attr_foreground_new(text_color.red,
271 text_color.green,
272 text_color.blue);
273 pango_attr_list_insert(attrs, fg_attr); // Ownership taken.
275 pango_layout_set_attributes(layout_, attrs); // Ref taken.
276 pango_attr_list_unref(attrs);
278 gfx::ScopedPangoFontDescription font_description(font.GetNativeFont());
279 pango_layout_set_font_description(layout_, font_description.get());
281 gtk_util::SetLayoutText(layout_, text);
283 // We add one pixel to the width because if the text fills up the width
284 // pango will try to split it over 2 lines.
285 int required_width = font.GetStringWidth(text) + 1;
287 pango_layout_set_width(layout_, required_width * PANGO_SCALE);
290 void AutofillPopupViewGtk::DrawSeparator(cairo_t* cairo_context,
291 const gfx::Rect& separator_rect) {
292 cairo_save(cairo_context);
293 cairo_move_to(cairo_context, 0, separator_rect.y());
294 cairo_line_to(cairo_context,
295 separator_rect.width(),
296 separator_rect.y() + separator_rect.height());
297 cairo_stroke(cairo_context);
298 cairo_restore(cairo_context);
301 void AutofillPopupViewGtk::DrawAutofillEntry(cairo_t* cairo_context,
302 size_t index,
303 const gfx::Rect& entry_rect) {
304 if (selected_line() == static_cast<int>(index)) {
305 gdk_cairo_set_source_color(cairo_context, &kHoveredBackgroundColor);
306 cairo_rectangle(cairo_context, entry_rect.x(), entry_rect.y(),
307 entry_rect.width(), entry_rect.height());
308 cairo_fill(cairo_context);
311 // Draw the value.
312 SetLayoutText(autofill_values()[index], value_font(), kValueTextColor);
313 int value_text_width = value_font().GetStringWidth(autofill_values()[index]);
315 // Center the text within the line.
316 int value_content_y = std::max(
317 entry_rect.y(),
318 entry_rect.y() + ((kRowHeight - value_font().GetHeight()) / 2));
320 bool is_rtl = base::i18n::IsRTL();
321 cairo_save(cairo_context);
322 cairo_move_to(cairo_context,
323 is_rtl ? entry_rect.width() - value_text_width - kEndPadding :
324 kEndPadding,
325 value_content_y);
326 pango_cairo_show_layout(cairo_context, layout_);
327 cairo_restore(cairo_context);
329 // Use this to figure out where all the other Autofill items should be placed.
330 int x_align_left = is_rtl ? kEndPadding : entry_rect.width() - kEndPadding;
332 // Draw the delete icon, if one is needed.
333 if (CanDelete(autofill_unique_ids()[index])) {
334 x_align_left += is_rtl ? 0 : -kDeleteIconWidth;
336 const gfx::Image* delete_icon;
337 if (static_cast<int>(index) == selected_line() && delete_icon_selected_)
338 delete_icon = theme_service_->GetImageNamed(IDR_CLOSE_BAR_H);
339 else
340 delete_icon = theme_service_->GetImageNamed(IDR_CLOSE_BAR);
342 // TODO(csharp): Create a custom resource for the delete icon.
343 // http://www.crbug.com/131801
344 cairo_save(cairo_context);
345 gtk_util::DrawFullImage(
346 cairo_context,
347 window_,
348 *delete_icon,
349 x_align_left,
350 entry_rect.y() + ((kRowHeight - kDeleteIconHeight) / 2));
351 cairo_restore(cairo_context);
352 cairo_save(cairo_context);
354 x_align_left += is_rtl ? kDeleteIconWidth + kIconPadding : -kIconPadding;
357 // Draw the Autofill icon, if one exists
358 if (!autofill_icons()[index].empty()) {
359 int icon = GetIconResourceID(autofill_icons()[index]);
360 DCHECK_NE(-1, icon);
361 int icon_y = entry_rect.y() + ((kRowHeight - kAutofillIconHeight) / 2);
363 x_align_left += is_rtl ? 0 : -kAutofillIconWidth;
365 cairo_save(cairo_context);
366 gtk_util::DrawFullImage(cairo_context,
367 window_,
368 *theme_service_->GetImageNamed(icon),
369 x_align_left,
370 icon_y);
371 cairo_restore(cairo_context);
373 x_align_left += is_rtl ? kAutofillIconWidth + kIconPadding : -kIconPadding;
376 // Draw the label text.
377 SetLayoutText(autofill_labels()[index], label_font(), kLabelTextColor);
378 x_align_left +=
379 is_rtl ? 0 : -label_font().GetStringWidth(autofill_labels()[index]);
381 // Center the text within the line.
382 int label_content_y = std::max(
383 entry_rect.y(),
384 entry_rect.y() + ((kRowHeight - label_font().GetHeight()) / 2));
386 cairo_save(cairo_context);
387 cairo_move_to(cairo_context, x_align_left, label_content_y);
388 pango_cairo_show_layout(cairo_context, layout_);
389 cairo_restore(cairo_context);
392 void AutofillPopupViewGtk::SetBounds() {
393 gint origin_x, origin_y;
394 gdk_window_get_origin(gtk_widget_get_window(parent_), &origin_x, &origin_y);
396 GdkScreen* screen = gtk_widget_get_screen(parent_);
397 gint screen_height = gdk_screen_get_height(screen);
399 int bottom_of_field = origin_y + element_bounds().y() +
400 element_bounds().height();
401 int popup_height = GetPopupRequiredHeight();
403 // Find the correct top position of the popup so that is doesn't go off
404 // the screen.
405 int top_of_popup = 0;
406 if (screen_height < bottom_of_field + popup_height) {
407 // The popup must appear above the field.
408 top_of_popup = origin_y + element_bounds().y() - popup_height;
409 } else {
410 // The popup can appear below the field.
411 top_of_popup = bottom_of_field;
414 bounds_.SetRect(
415 origin_x + element_bounds().x(),
416 top_of_popup,
417 GetPopupRequiredWidth(),
418 popup_height);
421 int AutofillPopupViewGtk::GetPopupRequiredWidth() {
422 int popup_width = element_bounds().width();
423 DCHECK_EQ(autofill_values().size(), autofill_labels().size());
424 for (size_t i = 0; i < autofill_values().size(); ++i) {
425 int row_size = kEndPadding +
426 value_font().GetStringWidth(autofill_values()[i]) +
427 kLabelPadding +
428 label_font().GetStringWidth(autofill_labels()[i]);
430 // Add the Autofill icon size, if required.
431 if (!autofill_icons()[i].empty())
432 row_size += kAutofillIconWidth + kIconPadding;
434 // Add delete icon, if required.
435 if (CanDelete(autofill_unique_ids()[i]))
436 row_size += kDeleteIconWidth + kIconPadding;
438 // Add the padding at the end
439 row_size += kEndPadding;
441 popup_width = std::max(popup_width, row_size);
444 return popup_width;
447 int AutofillPopupViewGtk::GetPopupRequiredHeight() {
448 int popup_height = 0;
450 for (size_t i = 0; i < autofill_unique_ids().size(); ++i) {
451 popup_height += GetRowHeight(autofill_unique_ids()[i]);
454 return popup_height;
457 int AutofillPopupViewGtk::LineFromY(int y) {
458 int current_height = 0;
460 for (size_t i = 0; i < autofill_unique_ids().size(); ++i) {
461 current_height += GetRowHeight(autofill_unique_ids()[i]);
463 if (y <= current_height)
464 return i;
467 // The y value goes beyond the popup so stop the selection at the last line.
468 return autofill_unique_ids().size() - 1;
471 gfx::Rect AutofillPopupViewGtk::GetRectForRow(size_t row, int width) {
472 int top = 0;
473 for (size_t i = 0; i < row; ++i) {
474 top += GetRowHeight(autofill_unique_ids()[i]);
477 return gfx::Rect(0, top, width, GetRowHeight(autofill_unique_ids()[row]));
480 bool AutofillPopupViewGtk::DeleteIconIsSelected(int x, int y) {
481 if (!CanDelete(selected_line()))
482 return false;
484 int row_start_y = 0;
485 for (int i = 0; i < selected_line(); ++i) {
486 row_start_y += GetRowHeight(autofill_unique_ids()[i]);
489 gfx::Rect delete_icon_bounds = gfx::Rect(
490 GetPopupRequiredWidth() - kDeleteIconWidth - kIconPadding,
491 row_start_y + ((kRowHeight - kDeleteIconHeight) / 2),
492 kDeleteIconWidth,
493 kDeleteIconHeight);
495 return delete_icon_bounds.Contains(x, y);