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