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 24 using WebKit::WebAutofillClient; 25 26 namespace { 27 28 const GdkColor kBorderColor = GDK_COLOR_RGB(0xc7, 0xca, 0xce); 29 const GdkColor kHoveredBackgroundColor = GDK_COLOR_RGB(0xcd, 0xcd, 0xcd); 30 const GdkColor kNameColor = GDK_COLOR_RGB(0x00, 0x00, 0x00); 31 const GdkColor kWarningColor = GDK_COLOR_RGB(0x7f, 0x7f, 0x7f); 32 const GdkColor kSubtextColor = GDK_COLOR_RGB(0x7f, 0x7f, 0x7f); 33 34 } // namespace 35 36 namespace autofill { 37 38 AutofillPopupViewGtk::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 69 AutofillPopupViewGtk::~AutofillPopupViewGtk() { 70 g_object_unref(layout_); 71 gtk_widget_destroy(window_); 72 } 73 74 void AutofillPopupViewGtk::Hide() { 75 delete this; 76 } 77 78 void 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 88 void 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 94 void 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 108 gboolean AutofillPopupViewGtk::HandleConfigure(GtkWidget* widget, 109 GdkEventConfigure* event) { 110 controller_->Hide(); 111 return FALSE; 112 } 113 114 gboolean 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 124 gboolean 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 159 gboolean AutofillPopupViewGtk::HandleLeave(GtkWidget* widget, 160 GdkEventCrossing* event) { 161 controller_->MouseExitedPopup(); 162 163 return FALSE; 164 } 165 166 gboolean AutofillPopupViewGtk::HandleMotion(GtkWidget* widget, 167 GdkEventMotion* event) { 168 controller_->MouseHovered(event->x, event->y); 169 170 return TRUE; 171 } 172 173 void 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 178 void 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 201 void 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 212 void 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 289 AutofillPopupView* AutofillPopupView::Create( 290 AutofillPopupController* controller) { 291 return new AutofillPopupViewGtk(controller); 292 } 293 294 } // namespace autofill 295