| OLD | NEW |
| (Empty) |
| 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 #include <pango/pango.h> | |
| 9 | |
| 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 "components/autofill/core/browser/popup_item_ids.h" | |
| 15 #include "grit/ui_resources.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" | |
| 26 | |
| 27 namespace { | |
| 28 | |
| 29 const GdkColor kBorderColor = GDK_COLOR_RGB(0xc7, 0xca, 0xce); | |
| 30 const GdkColor kHoveredBackgroundColor = GDK_COLOR_RGB(0xcd, 0xcd, 0xcd); | |
| 31 const GdkColor kNameColor = GDK_COLOR_RGB(0x00, 0x00, 0x00); | |
| 32 const GdkColor kWarningColor = GDK_COLOR_RGB(0x7f, 0x7f, 0x7f); | |
| 33 const GdkColor kSubtextColor = GDK_COLOR_RGB(0x7f, 0x7f, 0x7f); | |
| 34 | |
| 35 } // namespace | |
| 36 | |
| 37 namespace autofill { | |
| 38 | |
| 39 AutofillPopupViewGtk::AutofillPopupViewGtk( | |
| 40 AutofillPopupController* controller) | |
| 41 : controller_(controller), | |
| 42 window_(gtk_window_new(GTK_WINDOW_POPUP)) { | |
| 43 gtk_window_set_resizable(GTK_WINDOW(window_), FALSE); | |
| 44 gtk_widget_set_app_paintable(window_, TRUE); | |
| 45 gtk_widget_set_double_buffered(window_, TRUE); | |
| 46 | |
| 47 // Setup the window to ensure it receives the expose event. | |
| 48 gtk_widget_add_events(window_, GDK_BUTTON_MOTION_MASK | | |
| 49 GDK_BUTTON_RELEASE_MASK | | |
| 50 GDK_EXPOSURE_MASK | | |
| 51 GDK_POINTER_MOTION_MASK); | |
| 52 | |
| 53 GtkWidget* toplevel_window = gtk_widget_get_toplevel( | |
| 54 controller->container_view()); | |
| 55 signals_.Connect(toplevel_window, "configure-event", | |
| 56 G_CALLBACK(HandleConfigureThunk), this); | |
| 57 g_signal_connect(window_, "expose-event", | |
| 58 G_CALLBACK(HandleExposeThunk), this); | |
| 59 g_signal_connect(window_, "leave-notify-event", | |
| 60 G_CALLBACK(HandleLeaveThunk), this); | |
| 61 g_signal_connect(window_, "motion-notify-event", | |
| 62 G_CALLBACK(HandleMotionThunk), this); | |
| 63 g_signal_connect(window_, "button-release-event", | |
| 64 G_CALLBACK(HandleButtonReleaseThunk), this); | |
| 65 | |
| 66 // Cache the layout so we don't have to create it for every expose. | |
| 67 layout_ = gtk_widget_create_pango_layout(window_, NULL); | |
| 68 } | |
| 69 | |
| 70 AutofillPopupViewGtk::~AutofillPopupViewGtk() { | |
| 71 g_object_unref(layout_); | |
| 72 gtk_widget_destroy(window_); | |
| 73 } | |
| 74 | |
| 75 void AutofillPopupViewGtk::Hide() { | |
| 76 delete this; | |
| 77 } | |
| 78 | |
| 79 void AutofillPopupViewGtk::Show() { | |
| 80 UpdateBoundsAndRedrawPopup(); | |
| 81 | |
| 82 gtk_widget_show(window_); | |
| 83 | |
| 84 GtkWidget* parent_window = | |
| 85 gtk_widget_get_toplevel(controller_->container_view()); | |
| 86 ui::StackPopupWindow(window_, parent_window); | |
| 87 } | |
| 88 | |
| 89 void AutofillPopupViewGtk::InvalidateRow(size_t row) { | |
| 90 GdkRectangle row_rect = controller_->GetRowBounds(row).ToGdkRectangle(); | |
| 91 GdkWindow* gdk_window = gtk_widget_get_window(window_); | |
| 92 gdk_window_invalidate_rect(gdk_window, &row_rect, FALSE); | |
| 93 } | |
| 94 | |
| 95 void AutofillPopupViewGtk::UpdateBoundsAndRedrawPopup() { | |
| 96 gtk_widget_set_size_request(window_, | |
| 97 controller_->popup_bounds().width(), | |
| 98 controller_->popup_bounds().height()); | |
| 99 gtk_window_move(GTK_WINDOW(window_), | |
| 100 controller_->popup_bounds().x(), | |
| 101 controller_->popup_bounds().y()); | |
| 102 | |
| 103 GdkWindow* gdk_window = gtk_widget_get_window(window_); | |
| 104 GdkRectangle popup_rect = controller_->popup_bounds().ToGdkRectangle(); | |
| 105 if (gdk_window != NULL) | |
| 106 gdk_window_invalidate_rect(gdk_window, &popup_rect, FALSE); | |
| 107 } | |
| 108 | |
| 109 gboolean AutofillPopupViewGtk::HandleConfigure(GtkWidget* widget, | |
| 110 GdkEventConfigure* event) { | |
| 111 controller_->Hide(); | |
| 112 return FALSE; | |
| 113 } | |
| 114 | |
| 115 gboolean AutofillPopupViewGtk::HandleButtonRelease(GtkWidget* widget, | |
| 116 GdkEventButton* event) { | |
| 117 // We only care about the left click. | |
| 118 if (event->button != 1) | |
| 119 return FALSE; | |
| 120 | |
| 121 controller_->SetSelectionAtPoint(gfx::Point(event->x, event->y)); | |
| 122 controller_->AcceptSelectedLine(); | |
| 123 return TRUE; | |
| 124 } | |
| 125 | |
| 126 gboolean 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 // Draw the 1px border around the entire window. | |
| 133 gdk_cairo_set_source_color(cr, &kBorderColor); | |
| 134 gdk_cairo_rectangle(cr, &widget->allocation); | |
| 135 cairo_stroke(cr); | |
| 136 SetUpLayout(); | |
| 137 | |
| 138 gfx::Rect damage_rect(event->area); | |
| 139 | |
| 140 for (size_t i = 0; i < controller_->names().size(); ++i) { | |
| 141 gfx::Rect line_rect = controller_->GetRowBounds(i); | |
| 142 // Only repaint and layout damaged lines. | |
| 143 if (!line_rect.Intersects(damage_rect)) | |
| 144 continue; | |
| 145 | |
| 146 if (controller_->identifiers()[i] == POPUP_ITEM_ID_SEPARATOR) | |
| 147 DrawSeparator(cr, line_rect); | |
| 148 else | |
| 149 DrawAutofillEntry(cr, i, line_rect); | |
| 150 } | |
| 151 | |
| 152 cairo_destroy(cr); | |
| 153 | |
| 154 return TRUE; | |
| 155 } | |
| 156 | |
| 157 gboolean AutofillPopupViewGtk::HandleLeave(GtkWidget* widget, | |
| 158 GdkEventCrossing* event) { | |
| 159 controller_->SelectionCleared(); | |
| 160 | |
| 161 return FALSE; | |
| 162 } | |
| 163 | |
| 164 gboolean AutofillPopupViewGtk::HandleMotion(GtkWidget* widget, | |
| 165 GdkEventMotion* event) { | |
| 166 controller_->SetSelectionAtPoint(gfx::Point(event->x, event->y)); | |
| 167 | |
| 168 return TRUE; | |
| 169 } | |
| 170 | |
| 171 void AutofillPopupViewGtk::SetUpLayout() { | |
| 172 pango_layout_set_width(layout_, window_->allocation.width * PANGO_SCALE); | |
| 173 pango_layout_set_height(layout_, window_->allocation.height * PANGO_SCALE); | |
| 174 } | |
| 175 | |
| 176 void AutofillPopupViewGtk::SetLayoutText(const base::string16& text, | |
| 177 const gfx::FontList& font_list, | |
| 178 const GdkColor text_color) { | |
| 179 PangoAttrList* attrs = pango_attr_list_new(); | |
| 180 | |
| 181 PangoAttribute* fg_attr = pango_attr_foreground_new(text_color.red, | |
| 182 text_color.green, | |
| 183 text_color.blue); | |
| 184 pango_attr_list_insert(attrs, fg_attr); // Ownership taken. | |
| 185 | |
| 186 pango_layout_set_attributes(layout_, attrs); // Ref taken. | |
| 187 pango_attr_list_unref(attrs); | |
| 188 | |
| 189 gfx::ScopedPangoFontDescription font_description( | |
| 190 pango_font_description_from_string( | |
| 191 font_list.GetFontDescriptionString().c_str())); | |
| 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_->GetNameFontListForRow(index), | |
| 225 controller_->IsWarning(index) ? kWarningColor : kNameColor); | |
| 226 int value_text_width = | |
| 227 gfx::GetStringWidth(controller_->names()[index], | |
| 228 controller_->GetNameFontListForRow(index)); | |
| 229 | |
| 230 // Center the text within the line. | |
| 231 int row_height = entry_rect.height(); | |
| 232 int value_content_y = std::max( | |
| 233 entry_rect.y(), | |
| 234 entry_rect.y() + | |
| 235 (row_height - | |
| 236 controller_->GetNameFontListForRow(index).GetHeight()) / 2); | |
| 237 | |
| 238 bool is_rtl = controller_->IsRTL(); | |
| 239 int value_content_x = is_rtl ? | |
| 240 entry_rect.width() - value_text_width - kEndPadding : kEndPadding; | |
| 241 | |
| 242 cairo_save(cairo_context); | |
| 243 cairo_move_to(cairo_context, value_content_x, value_content_y); | |
| 244 pango_cairo_show_layout(cairo_context, layout_); | |
| 245 cairo_restore(cairo_context); | |
| 246 | |
| 247 // Use this to figure out where all the other Autofill items should be placed. | |
| 248 int x_align_left = is_rtl ? kEndPadding : entry_rect.width() - kEndPadding; | |
| 249 | |
| 250 // Draw the Autofill icon, if one exists | |
| 251 if (!controller_->icons()[index].empty()) { | |
| 252 int icon = controller_->GetIconResourceID(controller_->icons()[index]); | |
| 253 DCHECK_NE(-1, icon); | |
| 254 const gfx::Image& image = | |
| 255 ui::ResourceBundle::GetSharedInstance().GetImageNamed(icon); | |
| 256 int icon_y = entry_rect.y() + (row_height - image.Height()) / 2; | |
| 257 | |
| 258 x_align_left += is_rtl ? 0 : -image.Width(); | |
| 259 | |
| 260 cairo_save(cairo_context); | |
| 261 gtk_util::DrawFullImage(cairo_context, | |
| 262 window_, | |
| 263 image, | |
| 264 x_align_left, | |
| 265 icon_y); | |
| 266 cairo_restore(cairo_context); | |
| 267 | |
| 268 x_align_left += is_rtl ? image.Width() + kIconPadding : -kIconPadding; | |
| 269 } | |
| 270 | |
| 271 // Draw the subtext. | |
| 272 SetLayoutText(controller_->subtexts()[index], | |
| 273 controller_->subtext_font_list(), | |
| 274 kSubtextColor); | |
| 275 if (!is_rtl) { | |
| 276 x_align_left -= gfx::GetStringWidth(controller_->subtexts()[index], | |
| 277 controller_->subtext_font_list()); | |
| 278 } | |
| 279 | |
| 280 // Center the text within the line. | |
| 281 int subtext_content_y = std::max( | |
| 282 entry_rect.y(), | |
| 283 entry_rect.y() + | |
| 284 (row_height - controller_->subtext_font_list().GetHeight()) / 2); | |
| 285 | |
| 286 cairo_save(cairo_context); | |
| 287 cairo_move_to(cairo_context, x_align_left, subtext_content_y); | |
| 288 pango_cairo_show_layout(cairo_context, layout_); | |
| 289 cairo_restore(cairo_context); | |
| 290 } | |
| 291 | |
| 292 AutofillPopupView* AutofillPopupView::Create( | |
| 293 AutofillPopupController* controller) { | |
| 294 return new AutofillPopupViewGtk(controller); | |
| 295 } | |
| 296 | |
| 297 } // namespace autofill | |
| OLD | NEW |