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 |