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 "chrome/browser/ui/gtk/autofill/autofill_popup_view_gtk.h"
7 #include <gdk/gdkkeysyms.h>
8 #include <pango/pango.h>
10 #include "base/logging.h"
11 #include "base/strings/utf_string_conversions.h"
12 #include "chrome/browser/ui/autofill/autofill_popup_controller.h"
13 #include "chrome/browser/ui/gtk/gtk_util.h"
14 #include "grit/ui_resources.h"
15 #include "third_party/WebKit/public/web/WebAutofillClient.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/geometry/point.h"
20 #include "ui/gfx/geometry/rect.h"
21 #include "ui/gfx/gtk_compat.h"
22 #include "ui/gfx/image/image.h"
23 #include "ui/gfx/native_widget_types.h"
24 #include "ui/gfx/pango_util.h"
25 #include "ui/gfx/text_utils.h"
27 using blink::WebAutofillClient;
31 const GdkColor kBorderColor = GDK_COLOR_RGB(0xc7, 0xca, 0xce);
32 const GdkColor kHoveredBackgroundColor = GDK_COLOR_RGB(0xcd, 0xcd, 0xcd);
33 const GdkColor kNameColor = GDK_COLOR_RGB(0x00, 0x00, 0x00);
34 const GdkColor kWarningColor = GDK_COLOR_RGB(0x7f, 0x7f, 0x7f);
35 const GdkColor kSubtextColor = GDK_COLOR_RGB(0x7f, 0x7f, 0x7f);
41 AutofillPopupViewGtk::AutofillPopupViewGtk(
42 AutofillPopupController* controller)
43 : controller_(controller),
44 window_(gtk_window_new(GTK_WINDOW_POPUP)) {
45 gtk_window_set_resizable(GTK_WINDOW(window_), FALSE);
46 gtk_widget_set_app_paintable(window_, TRUE);
47 gtk_widget_set_double_buffered(window_, TRUE);
49 // Setup the window to ensure it receives the expose event.
50 gtk_widget_add_events(window_, GDK_BUTTON_MOTION_MASK |
51 GDK_BUTTON_RELEASE_MASK |
53 GDK_POINTER_MOTION_MASK);
55 GtkWidget* toplevel_window = gtk_widget_get_toplevel(
56 controller->container_view());
57 signals_.Connect(toplevel_window, "configure-event",
58 G_CALLBACK(HandleConfigureThunk), this);
59 g_signal_connect(window_, "expose-event",
60 G_CALLBACK(HandleExposeThunk), this);
61 g_signal_connect(window_, "leave-notify-event",
62 G_CALLBACK(HandleLeaveThunk), this);
63 g_signal_connect(window_, "motion-notify-event",
64 G_CALLBACK(HandleMotionThunk), this);
65 g_signal_connect(window_, "button-release-event",
66 G_CALLBACK(HandleButtonReleaseThunk), this);
68 // Cache the layout so we don't have to create it for every expose.
69 layout_ = gtk_widget_create_pango_layout(window_, NULL);
72 AutofillPopupViewGtk::~AutofillPopupViewGtk() {
73 g_object_unref(layout_);
74 gtk_widget_destroy(window_);
77 void AutofillPopupViewGtk::Hide() {
81 void AutofillPopupViewGtk::Show() {
82 UpdateBoundsAndRedrawPopup();
84 gtk_widget_show(window_);
86 GtkWidget* parent_window =
87 gtk_widget_get_toplevel(controller_->container_view());
88 ui::StackPopupWindow(window_, parent_window);
91 void AutofillPopupViewGtk::InvalidateRow(size_t row) {
92 GdkRectangle row_rect = controller_->GetRowBounds(row).ToGdkRectangle();
93 GdkWindow* gdk_window = gtk_widget_get_window(window_);
94 gdk_window_invalidate_rect(gdk_window, &row_rect, FALSE);
97 void AutofillPopupViewGtk::UpdateBoundsAndRedrawPopup() {
98 gtk_widget_set_size_request(window_,
99 controller_->popup_bounds().width(),
100 controller_->popup_bounds().height());
101 gtk_window_move(GTK_WINDOW(window_),
102 controller_->popup_bounds().x(),
103 controller_->popup_bounds().y());
105 GdkWindow* gdk_window = gtk_widget_get_window(window_);
106 GdkRectangle popup_rect = controller_->popup_bounds().ToGdkRectangle();
107 if (gdk_window != NULL)
108 gdk_window_invalidate_rect(gdk_window, &popup_rect, FALSE);
111 gboolean AutofillPopupViewGtk::HandleConfigure(GtkWidget* widget,
112 GdkEventConfigure* event) {
117 gboolean AutofillPopupViewGtk::HandleButtonRelease(GtkWidget* widget,
118 GdkEventButton* event) {
119 // We only care about the left click.
120 if (event->button != 1)
123 controller_->AcceptSelectionAtPoint(gfx::Point(event->x, event->y));
127 gboolean AutofillPopupViewGtk::HandleExpose(GtkWidget* widget,
128 GdkEventExpose* event) {
129 cairo_t* cr = gdk_cairo_create(GDK_DRAWABLE(gtk_widget_get_window(widget)));
130 gdk_cairo_rectangle(cr, &event->area);
133 // Draw the 1px border around the entire window.
134 gdk_cairo_set_source_color(cr, &kBorderColor);
135 gdk_cairo_rectangle(cr, &widget->allocation);
139 gfx::Rect damage_rect(event->area);
141 for (size_t i = 0; i < controller_->names().size(); ++i) {
142 gfx::Rect line_rect = controller_->GetRowBounds(i);
143 // Only repaint and layout damaged lines.
144 if (!line_rect.Intersects(damage_rect))
147 if (controller_->identifiers()[i] == WebAutofillClient::MenuItemIDSeparator)
148 DrawSeparator(cr, line_rect);
150 DrawAutofillEntry(cr, i, line_rect);
158 gboolean AutofillPopupViewGtk::HandleLeave(GtkWidget* widget,
159 GdkEventCrossing* event) {
160 controller_->SelectionCleared();
165 gboolean AutofillPopupViewGtk::HandleMotion(GtkWidget* widget,
166 GdkEventMotion* event) {
167 controller_->AcceptSelectionAtPoint(gfx::Point(event->x, event->y));
172 void AutofillPopupViewGtk::SetUpLayout() {
173 pango_layout_set_width(layout_, window_->allocation.width * PANGO_SCALE);
174 pango_layout_set_height(layout_, window_->allocation.height * PANGO_SCALE);
177 void AutofillPopupViewGtk::SetLayoutText(const base::string16& text,
178 const gfx::FontList& font_list,
179 const GdkColor text_color) {
180 PangoAttrList* attrs = pango_attr_list_new();
182 PangoAttribute* fg_attr = pango_attr_foreground_new(text_color.red,
185 pango_attr_list_insert(attrs, fg_attr); // Ownership taken.
187 pango_layout_set_attributes(layout_, attrs); // Ref taken.
188 pango_attr_list_unref(attrs);
190 gfx::ScopedPangoFontDescription font_description(
191 pango_font_description_from_string(
192 font_list.GetFontDescriptionString().c_str()));
193 pango_layout_set_font_description(layout_, font_description.get());
195 gtk_util::SetLayoutText(layout_, text);
197 // The popup is already the correct size for the text, so set the width to -1
198 // to prevent additional wrapping or ellipsization.
199 pango_layout_set_width(layout_, -1);
202 void AutofillPopupViewGtk::DrawSeparator(cairo_t* cairo_context,
203 const gfx::Rect& separator_rect) {
204 cairo_save(cairo_context);
205 cairo_move_to(cairo_context, 0, separator_rect.y());
206 cairo_line_to(cairo_context,
207 separator_rect.width(),
208 separator_rect.y() + separator_rect.height());
209 cairo_stroke(cairo_context);
210 cairo_restore(cairo_context);
213 void AutofillPopupViewGtk::DrawAutofillEntry(cairo_t* cairo_context,
215 const gfx::Rect& entry_rect) {
216 if (controller_->selected_line() == static_cast<int>(index)) {
217 gdk_cairo_set_source_color(cairo_context, &kHoveredBackgroundColor);
218 cairo_rectangle(cairo_context, entry_rect.x(), entry_rect.y(),
219 entry_rect.width(), entry_rect.height());
220 cairo_fill(cairo_context);
224 SetLayoutText(controller_->names()[index],
225 controller_->GetNameFontListForRow(index),
226 controller_->IsWarning(index) ? kWarningColor : kNameColor);
227 int value_text_width =
228 gfx::GetStringWidth(controller_->names()[index],
229 controller_->GetNameFontListForRow(index));
231 // Center the text within the line.
232 int row_height = entry_rect.height();
233 int value_content_y = std::max(
237 controller_->GetNameFontListForRow(index).GetHeight()) / 2);
239 bool is_rtl = controller_->IsRTL();
240 int value_content_x = is_rtl ?
241 entry_rect.width() - value_text_width - kEndPadding : kEndPadding;
243 cairo_save(cairo_context);
244 cairo_move_to(cairo_context, value_content_x, value_content_y);
245 pango_cairo_show_layout(cairo_context, layout_);
246 cairo_restore(cairo_context);
248 // Use this to figure out where all the other Autofill items should be placed.
249 int x_align_left = is_rtl ? kEndPadding : entry_rect.width() - kEndPadding;
251 // Draw the Autofill icon, if one exists
252 if (!controller_->icons()[index].empty()) {
253 int icon = controller_->GetIconResourceID(controller_->icons()[index]);
255 const gfx::Image& image =
256 ui::ResourceBundle::GetSharedInstance().GetImageNamed(icon);
257 int icon_y = entry_rect.y() + (row_height - image.Height()) / 2;
259 x_align_left += is_rtl ? 0 : -image.Width();
261 cairo_save(cairo_context);
262 gtk_util::DrawFullImage(cairo_context,
267 cairo_restore(cairo_context);
269 x_align_left += is_rtl ? image.Width() + kIconPadding : -kIconPadding;
273 SetLayoutText(controller_->subtexts()[index],
274 controller_->subtext_font_list(),
277 x_align_left -= gfx::GetStringWidth(controller_->subtexts()[index],
278 controller_->subtext_font_list());
281 // Center the text within the line.
282 int subtext_content_y = std::max(
285 (row_height - controller_->subtext_font_list().GetHeight()) / 2);
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);
293 AutofillPopupView* AutofillPopupView::Create(
294 AutofillPopupController* controller) {
295 return new AutofillPopupViewGtk(controller);
298 } // namespace autofill