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/gtk_util.h" | |
6 | |
7 #include <cairo/cairo.h> | |
8 | |
9 #include <algorithm> | |
10 #include <cstdarg> | |
11 #include <map> | |
12 | |
13 #include "base/environment.h" | |
14 #include "base/i18n/rtl.h" | |
15 #include "base/logging.h" | |
16 #include "base/nix/xdg_util.h" | |
17 #include "base/strings/string_number_conversions.h" | |
18 #include "base/strings/utf_string_conversions.h" | |
19 #include "chrome/browser/autocomplete/autocomplete_classifier.h" | |
20 #include "chrome/browser/autocomplete/autocomplete_classifier_factory.h" | |
21 #include "chrome/browser/autocomplete/autocomplete_input.h" | |
22 #include "chrome/browser/autocomplete/autocomplete_match.h" | |
23 #include "chrome/browser/browser_process.h" | |
24 #include "chrome/browser/profiles/profile.h" | |
25 #include "chrome/browser/profiles/profile_info_cache.h" | |
26 #include "chrome/browser/profiles/profile_manager.h" | |
27 #include "chrome/browser/profiles/profiles_state.h" | |
28 #include "chrome/browser/themes/theme_properties.h" | |
29 #include "chrome/browser/ui/browser.h" | |
30 #include "chrome/browser/ui/browser_iterator.h" | |
31 #include "chrome/browser/ui/browser_list.h" | |
32 #include "chrome/browser/ui/browser_window.h" | |
33 #include "chrome/browser/ui/gtk/browser_window_gtk.h" | |
34 #include "chrome/browser/ui/gtk/gtk_theme_service.h" | |
35 #include "chrome/browser/ui/host_desktop.h" | |
36 #include "grit/chrome_unscaled_resources.h" | |
37 #include "grit/theme_resources.h" | |
38 #include "ui/base/accelerators/menu_label_accelerator_util_linux.h" | |
39 #include "ui/base/gtk/gtk_hig_constants.h" | |
40 #include "ui/base/gtk/gtk_screen_util.h" | |
41 #include "ui/base/l10n/l10n_util.h" | |
42 #include "ui/base/resource/resource_bundle.h" | |
43 #include "ui/base/x/x11_util.h" | |
44 #include "ui/gfx/gtk_compat.h" | |
45 #include "ui/gfx/image/cairo_cached_surface.h" | |
46 #include "ui/gfx/image/image.h" | |
47 #include "ui/gfx/pango_util.h" | |
48 #include "ui/gfx/text_elider.h" | |
49 #include "url/gurl.h" | |
50 | |
51 // These conflict with base/tracked_objects.h, so need to come last. | |
52 #include <gdk/gdkx.h> // NOLINT | |
53 | |
54 namespace { | |
55 | |
56 #if defined(GOOGLE_CHROME_BUILD) | |
57 static const char* kIconName = "google-chrome"; | |
58 #else | |
59 static const char* kIconName = "chromium-browser"; | |
60 #endif | |
61 | |
62 const char kBoldLabelMarkup[] = "<span weight='bold'>%s</span>"; | |
63 | |
64 // Max size of each component of the button tooltips. | |
65 const size_t kMaxTooltipTitleLength = 100; | |
66 const size_t kMaxTooltipURLLength = 400; | |
67 | |
68 // Callback used in RemoveAllChildren. | |
69 void RemoveWidget(GtkWidget* widget, gpointer container) { | |
70 gtk_container_remove(GTK_CONTAINER(container), widget); | |
71 } | |
72 | |
73 // These two functions are copped almost directly from gtk core. The only | |
74 // difference is that they accept middle clicks. | |
75 gboolean OnMouseButtonPressed(GtkWidget* widget, GdkEventButton* event, | |
76 gpointer userdata) { | |
77 if (event->type == GDK_BUTTON_PRESS) { | |
78 if (gtk_button_get_focus_on_click(GTK_BUTTON(widget)) && | |
79 !gtk_widget_has_focus(widget)) { | |
80 gtk_widget_grab_focus(widget); | |
81 } | |
82 | |
83 gint button_mask = GPOINTER_TO_INT(userdata); | |
84 if (button_mask & (1 << event->button)) | |
85 gtk_button_pressed(GTK_BUTTON(widget)); | |
86 } | |
87 | |
88 return TRUE; | |
89 } | |
90 | |
91 gboolean OnMouseButtonReleased(GtkWidget* widget, GdkEventButton* event, | |
92 gpointer userdata) { | |
93 gint button_mask = GPOINTER_TO_INT(userdata); | |
94 if (button_mask && (1 << event->button)) | |
95 gtk_button_released(GTK_BUTTON(widget)); | |
96 | |
97 return TRUE; | |
98 } | |
99 | |
100 // Returns the approximate number of characters that can horizontally fit in | |
101 // |pixel_width| pixels. | |
102 int GetCharacterWidthForPixels(GtkWidget* widget, int pixel_width) { | |
103 DCHECK(gtk_widget_get_realized(widget)) | |
104 << " widget must be realized to compute font metrics correctly"; | |
105 | |
106 PangoContext* context = gtk_widget_create_pango_context(widget); | |
107 GtkStyle* style = gtk_widget_get_style(widget); | |
108 PangoFontMetrics* metrics = pango_context_get_metrics(context, | |
109 style->font_desc, pango_context_get_language(context)); | |
110 | |
111 // This technique (max of char and digit widths) matches the code in | |
112 // gtklabel.c. | |
113 int char_width = pixel_width * PANGO_SCALE / | |
114 std::max(pango_font_metrics_get_approximate_char_width(metrics), | |
115 pango_font_metrics_get_approximate_digit_width(metrics)); | |
116 | |
117 pango_font_metrics_unref(metrics); | |
118 g_object_unref(context); | |
119 | |
120 return char_width; | |
121 } | |
122 | |
123 void OnLabelRealize(GtkWidget* label, gpointer pixel_width) { | |
124 gtk_label_set_width_chars( | |
125 GTK_LABEL(label), | |
126 GetCharacterWidthForPixels(label, GPOINTER_TO_INT(pixel_width))); | |
127 } | |
128 | |
129 // Ownership of |icon_list| is passed to the caller. | |
130 GList* GetIconList() { | |
131 ui::ResourceBundle& rb = ui::ResourceBundle::GetSharedInstance(); | |
132 GList* icon_list = NULL; | |
133 icon_list = g_list_append(icon_list, | |
134 rb.GetNativeImageNamed(IDR_PRODUCT_LOGO_32).ToGdkPixbuf()); | |
135 icon_list = g_list_append(icon_list, | |
136 rb.GetNativeImageNamed(IDR_PRODUCT_LOGO_16).ToGdkPixbuf()); | |
137 return icon_list; | |
138 } | |
139 | |
140 // Returns the avatar icon for |profile|. | |
141 // | |
142 // Returns NULL if there is only one profile; always returns an icon for | |
143 // Incognito profiles. | |
144 // | |
145 // The returned pixbuf must not be unreferenced or freed because it's owned by | |
146 // either the resource bundle or the profile info cache. | |
147 GdkPixbuf* GetAvatarIcon(Profile* profile) { | |
148 if (profile->IsOffTheRecord()) { | |
149 ui::ResourceBundle& rb = ui::ResourceBundle::GetSharedInstance(); | |
150 return rb.GetNativeImageNamed(IDR_OTR_ICON).ToGdkPixbuf(); | |
151 } | |
152 | |
153 const ProfileInfoCache& cache = | |
154 g_browser_process->profile_manager()->GetProfileInfoCache(); | |
155 | |
156 if (!profiles::IsMultipleProfilesEnabled() || | |
157 cache.GetNumberOfProfiles() < 2) | |
158 return NULL; | |
159 | |
160 const size_t index = cache.GetIndexOfProfileWithPath(profile->GetPath()); | |
161 | |
162 return (index != std::string::npos ? | |
163 cache.GetAvatarIconOfProfileAtIndex(index).ToGdkPixbuf() : | |
164 static_cast<GdkPixbuf*>(NULL)); | |
165 } | |
166 | |
167 // Gets the Chrome product icon. | |
168 // | |
169 // If it doesn't find the icon in |theme|, it looks among the icons packaged | |
170 // with Chrome. | |
171 // | |
172 // Supported values of |size| are 16, 32, and 64. If the Chrome icon is found | |
173 // in |theme|, the returned icon may not be of the requested size if |size| | |
174 // has an unsupported value (GTK might scale it). If the Chrome icon is not | |
175 // found in |theme|, and |size| has an unsupported value, the program will be | |
176 // aborted with CHECK(false). | |
177 // | |
178 // The caller is responsible for calling g_object_unref() on the returned | |
179 // pixbuf. | |
180 GdkPixbuf* GetChromeIcon(GtkIconTheme* theme, const int size) { | |
181 if (gtk_icon_theme_has_icon(theme, kIconName)) { | |
182 GdkPixbuf* icon = | |
183 gtk_icon_theme_load_icon(theme, | |
184 kIconName, | |
185 size, | |
186 static_cast<GtkIconLookupFlags>(0), | |
187 0); | |
188 GdkPixbuf* icon_copy = gdk_pixbuf_copy(icon); | |
189 g_object_unref(icon); | |
190 return icon_copy; | |
191 } | |
192 | |
193 ui::ResourceBundle& rb = ui::ResourceBundle::GetSharedInstance(); | |
194 int id = 0; | |
195 | |
196 switch (size) { | |
197 case 16: id = IDR_PRODUCT_LOGO_16; break; | |
198 case 32: id = IDR_PRODUCT_LOGO_32; break; | |
199 case 64: id = IDR_PRODUCT_LOGO_64; break; | |
200 default: CHECK(false); break; | |
201 } | |
202 | |
203 return gdk_pixbuf_copy(rb.GetNativeImageNamed(id).ToGdkPixbuf()); | |
204 } | |
205 | |
206 // Adds |emblem| to the bottom-right corner of |icon|. | |
207 // | |
208 // Taking the ceiling of the scaled destination rect's dimensions (|dest_w| | |
209 // and |dest_h|) because, if the destination rect is larger than the scaled | |
210 // emblem, gdk_pixbuf_composite() will replicate the edge pixels of the emblem | |
211 // to fill the gap, which is better than a cropped emblem, I think. | |
212 void AddEmblem(const GdkPixbuf* emblem, GdkPixbuf* icon) { | |
213 const int iw = gdk_pixbuf_get_width(icon); | |
214 const int ih = gdk_pixbuf_get_height(icon); | |
215 const int ew = gdk_pixbuf_get_width(emblem); | |
216 const int eh = gdk_pixbuf_get_height(emblem); | |
217 | |
218 const double emblem_scale = | |
219 (static_cast<double>(ih) / static_cast<double>(eh)) * 0.5; | |
220 const int dest_w = ::ceil(ew * emblem_scale); | |
221 const int dest_h = ::ceil(eh * emblem_scale); | |
222 const int x = iw - dest_w; // Used for offset_x and dest_x. | |
223 const int y = ih - dest_h; // Used for offset_y and dest_y. | |
224 | |
225 gdk_pixbuf_composite(emblem, icon, | |
226 x, y, | |
227 dest_w, dest_h, | |
228 x, y, | |
229 emblem_scale, emblem_scale, | |
230 GDK_INTERP_BILINEAR, 255); | |
231 } | |
232 | |
233 // Returns a list containing Chrome icons of various sizes emblemed with the | |
234 // |profile|'s avatar. | |
235 // | |
236 // If there is only one profile, no emblem is added, but icons for Incognito | |
237 // profiles will always get the Incognito emblem. | |
238 // | |
239 // The caller owns the list and all the icons it contains will have had their | |
240 // reference counts incremented. Therefore the caller should unreference each | |
241 // element before freeing the list. | |
242 GList* GetIconListWithAvatars(GtkWindow* window, Profile* profile) { | |
243 GtkIconTheme* theme = | |
244 gtk_icon_theme_get_for_screen(gtk_widget_get_screen(GTK_WIDGET(window))); | |
245 | |
246 GdkPixbuf* icon_16 = GetChromeIcon(theme, 16); | |
247 GdkPixbuf* icon_32 = GetChromeIcon(theme, 32); | |
248 GdkPixbuf* icon_64 = GetChromeIcon(theme, 64); | |
249 | |
250 const GdkPixbuf* avatar = GetAvatarIcon(profile); | |
251 if (avatar) { | |
252 AddEmblem(avatar, icon_16); | |
253 AddEmblem(avatar, icon_32); | |
254 AddEmblem(avatar, icon_64); | |
255 } | |
256 | |
257 GList* icon_list = NULL; | |
258 icon_list = g_list_append(icon_list, icon_64); | |
259 icon_list = g_list_append(icon_list, icon_32); | |
260 icon_list = g_list_append(icon_list, icon_16); | |
261 | |
262 return icon_list; | |
263 } | |
264 | |
265 // Expose event handler for a container that simply suppresses the default | |
266 // drawing and propagates the expose event to the container's children. | |
267 gboolean PaintNoBackground(GtkWidget* widget, | |
268 GdkEventExpose* event, | |
269 gpointer unused) { | |
270 GList* children = gtk_container_get_children(GTK_CONTAINER(widget)); | |
271 for (GList* item = children; item; item = item->next) { | |
272 gtk_container_propagate_expose(GTK_CONTAINER(widget), | |
273 GTK_WIDGET(item->data), | |
274 event); | |
275 } | |
276 g_list_free(children); | |
277 | |
278 return TRUE; | |
279 } | |
280 | |
281 } // namespace | |
282 | |
283 namespace gtk_util { | |
284 | |
285 GtkWidget* CreateLabeledControlsGroup(std::vector<GtkWidget*>* labels, | |
286 const char* text, ...) { | |
287 va_list ap; | |
288 va_start(ap, text); | |
289 GtkWidget* table = gtk_table_new(0, 2, FALSE); | |
290 gtk_table_set_col_spacing(GTK_TABLE(table), 0, ui::kLabelSpacing); | |
291 gtk_table_set_row_spacings(GTK_TABLE(table), ui::kControlSpacing); | |
292 | |
293 for (guint row = 0; text; ++row) { | |
294 gtk_table_resize(GTK_TABLE(table), row + 1, 2); | |
295 GtkWidget* control = va_arg(ap, GtkWidget*); | |
296 GtkWidget* label = gtk_label_new(text); | |
297 gtk_misc_set_alignment(GTK_MISC(label), 0, 0.5); | |
298 if (labels) | |
299 labels->push_back(label); | |
300 | |
301 gtk_table_attach(GTK_TABLE(table), label, | |
302 0, 1, row, row + 1, | |
303 GTK_FILL, GTK_FILL, | |
304 0, 0); | |
305 gtk_table_attach_defaults(GTK_TABLE(table), control, | |
306 1, 2, row, row + 1); | |
307 text = va_arg(ap, const char*); | |
308 } | |
309 va_end(ap); | |
310 | |
311 return table; | |
312 } | |
313 | |
314 GtkWidget* CreateGtkBorderBin(GtkWidget* child, const GdkColor* color, | |
315 int top, int bottom, int left, int right) { | |
316 // Use a GtkEventBox to get the background painted. However, we can't just | |
317 // use a container border, since it won't paint there. Use an alignment | |
318 // inside to get the sizes exactly of how we want the border painted. | |
319 GtkWidget* ebox = gtk_event_box_new(); | |
320 if (color) | |
321 gtk_widget_modify_bg(ebox, GTK_STATE_NORMAL, color); | |
322 GtkWidget* alignment = gtk_alignment_new(0.0, 0.0, 1.0, 1.0); | |
323 gtk_alignment_set_padding(GTK_ALIGNMENT(alignment), top, bottom, left, right); | |
324 gtk_container_add(GTK_CONTAINER(alignment), child); | |
325 gtk_container_add(GTK_CONTAINER(ebox), alignment); | |
326 return ebox; | |
327 } | |
328 | |
329 GtkWidget* LeftAlignMisc(GtkWidget* misc) { | |
330 gtk_misc_set_alignment(GTK_MISC(misc), 0, 0.5); | |
331 return misc; | |
332 } | |
333 | |
334 GtkWidget* CreateBoldLabel(const std::string& text) { | |
335 GtkWidget* label = gtk_label_new(NULL); | |
336 char* markup = g_markup_printf_escaped(kBoldLabelMarkup, text.c_str()); | |
337 gtk_label_set_markup(GTK_LABEL(label), markup); | |
338 g_free(markup); | |
339 | |
340 return LeftAlignMisc(label); | |
341 } | |
342 | |
343 void GetWidgetSizeFromCharacters( | |
344 GtkWidget* widget, double width_chars, double height_lines, | |
345 int* width, int* height) { | |
346 DCHECK(gtk_widget_get_realized(widget)) | |
347 << " widget must be realized to compute font metrics correctly"; | |
348 PangoContext* context = gtk_widget_create_pango_context(widget); | |
349 GtkStyle* style = gtk_widget_get_style(widget); | |
350 PangoFontMetrics* metrics = pango_context_get_metrics(context, | |
351 style->font_desc, pango_context_get_language(context)); | |
352 if (width) { | |
353 *width = static_cast<int>( | |
354 pango_font_metrics_get_approximate_char_width(metrics) * | |
355 width_chars / PANGO_SCALE); | |
356 } | |
357 if (height) { | |
358 *height = static_cast<int>( | |
359 (pango_font_metrics_get_ascent(metrics) + | |
360 pango_font_metrics_get_descent(metrics)) * | |
361 height_lines / PANGO_SCALE); | |
362 } | |
363 pango_font_metrics_unref(metrics); | |
364 g_object_unref(context); | |
365 } | |
366 | |
367 void GetWidgetSizeFromResources( | |
368 GtkWidget* widget, int width_chars, int height_lines, | |
369 int* width, int* height) { | |
370 DCHECK(gtk_widget_get_realized(widget)) | |
371 << " widget must be realized to compute font metrics correctly"; | |
372 | |
373 double chars = 0; | |
374 if (width) | |
375 base::StringToDouble(l10n_util::GetStringUTF8(width_chars), &chars); | |
376 | |
377 double lines = 0; | |
378 if (height) | |
379 base::StringToDouble(l10n_util::GetStringUTF8(height_lines), &lines); | |
380 | |
381 GetWidgetSizeFromCharacters(widget, chars, lines, width, height); | |
382 } | |
383 | |
384 void SetWindowSizeFromResources(GtkWindow* window, | |
385 int width_id, int height_id, bool resizable) { | |
386 int width = -1; | |
387 int height = -1; | |
388 gtk_util::GetWidgetSizeFromResources(GTK_WIDGET(window), width_id, height_id, | |
389 (width_id != -1) ? &width : NULL, | |
390 (height_id != -1) ? &height : NULL); | |
391 | |
392 if (resizable) { | |
393 gtk_window_set_default_size(window, width, height); | |
394 } else { | |
395 // For a non-resizable window, GTK tries to snap the window size | |
396 // to the minimum size around the content. We use the sizes in | |
397 // the resources to set *minimum* window size to allow windows | |
398 // with long titles to be wide enough to display their titles. | |
399 // | |
400 // But if GTK wants to make the window *wider* due to very wide | |
401 // controls, we should allow that too, so be careful to pick the | |
402 // wider of the resources size and the natural window size. | |
403 | |
404 gtk_widget_show_all(gtk_bin_get_child(GTK_BIN(window))); | |
405 GtkRequisition requisition; | |
406 gtk_widget_size_request(GTK_WIDGET(window), &requisition); | |
407 gtk_widget_set_size_request( | |
408 GTK_WIDGET(window), | |
409 width == -1 ? -1 : std::max(width, requisition.width), | |
410 height == -1 ? -1 : std::max(height, requisition.height)); | |
411 } | |
412 gtk_window_set_resizable(window, resizable ? TRUE : FALSE); | |
413 } | |
414 | |
415 void MakeAppModalWindowGroup() { | |
416 // Older versions of GTK+ don't give us gtk_window_group_list() which is what | |
417 // we need to add current non-browser modal dialogs to the list. If | |
418 // we have 2.14+ we can do things the correct way. | |
419 GtkWindowGroup* window_group = gtk_window_group_new(); | |
420 for (chrome::BrowserIterator it; !it.done(); it.Next()) { | |
421 // List all windows in this current group | |
422 GtkWindowGroup* old_group = | |
423 gtk_window_get_group((*it)->window()->GetNativeWindow()); | |
424 | |
425 GList* all_windows = gtk_window_group_list_windows(old_group); | |
426 for (GList* window = all_windows; window; window = window->next) { | |
427 gtk_window_group_add_window(window_group, GTK_WINDOW(window->data)); | |
428 } | |
429 g_list_free(all_windows); | |
430 } | |
431 g_object_unref(window_group); | |
432 } | |
433 | |
434 void AppModalDismissedUngroupWindows() { | |
435 // GTK only has the native desktop. | |
436 const BrowserList* native_browser_list = | |
437 BrowserList::GetInstance(chrome::HOST_DESKTOP_TYPE_NATIVE); | |
438 if (!native_browser_list->empty()) { | |
439 std::vector<GtkWindow*> transient_windows; | |
440 | |
441 // All windows should be part of one big modal group right now. | |
442 GtkWindowGroup* window_group = gtk_window_get_group( | |
443 native_browser_list->get(0)->window()->GetNativeWindow()); | |
444 GList* windows = gtk_window_group_list_windows(window_group); | |
445 | |
446 for (GList* item = windows; item; item = item->next) { | |
447 GtkWindow* window = GTK_WINDOW(item->data); | |
448 GtkWindow* transient_for = gtk_window_get_transient_for(window); | |
449 if (transient_for) { | |
450 transient_windows.push_back(window); | |
451 } else { | |
452 GtkWindowGroup* window_group = gtk_window_group_new(); | |
453 gtk_window_group_add_window(window_group, window); | |
454 g_object_unref(window_group); | |
455 } | |
456 } | |
457 | |
458 // Put each transient window in the same group as its transient parent. | |
459 for (std::vector<GtkWindow*>::iterator it = transient_windows.begin(); | |
460 it != transient_windows.end(); ++it) { | |
461 GtkWindow* transient_parent = gtk_window_get_transient_for(*it); | |
462 GtkWindowGroup* group = gtk_window_get_group(transient_parent); | |
463 gtk_window_group_add_window(group, *it); | |
464 } | |
465 g_list_free(windows); | |
466 } | |
467 } | |
468 | |
469 void RemoveAllChildren(GtkWidget* container) { | |
470 gtk_container_foreach(GTK_CONTAINER(container), RemoveWidget, container); | |
471 } | |
472 | |
473 void ForceFontSizePixels(GtkWidget* widget, double size_pixels) { | |
474 gfx::ScopedPangoFontDescription font_desc(pango_font_description_new()); | |
475 // pango_font_description_set_absolute_size sets the font size in device | |
476 // units, which for us is pixels. | |
477 pango_font_description_set_absolute_size(font_desc.get(), | |
478 PANGO_SCALE * size_pixels); | |
479 gtk_widget_modify_font(widget, font_desc.get()); | |
480 } | |
481 | |
482 void UndoForceFontSize(GtkWidget* widget) { | |
483 gtk_widget_modify_font(widget, NULL); | |
484 } | |
485 | |
486 gfx::Size GetWidgetSize(GtkWidget* widget) { | |
487 GtkRequisition size; | |
488 gtk_widget_size_request(widget, &size); | |
489 return gfx::Size(size.width, size.height); | |
490 } | |
491 | |
492 void ConvertWidgetPointToScreen(GtkWidget* widget, gfx::Point* p) { | |
493 DCHECK(widget); | |
494 DCHECK(p); | |
495 | |
496 *p += ui::GetWidgetScreenOffset(widget); | |
497 } | |
498 | |
499 GtkWidget* CenterWidgetInHBox(GtkWidget* hbox, GtkWidget* widget, | |
500 bool pack_at_end, int padding) { | |
501 GtkWidget* centering_vbox = gtk_vbox_new(FALSE, 0); | |
502 gtk_box_pack_start(GTK_BOX(centering_vbox), widget, TRUE, FALSE, 0); | |
503 if (pack_at_end) | |
504 gtk_box_pack_end(GTK_BOX(hbox), centering_vbox, FALSE, FALSE, padding); | |
505 else | |
506 gtk_box_pack_start(GTK_BOX(hbox), centering_vbox, FALSE, FALSE, padding); | |
507 | |
508 return centering_vbox; | |
509 } | |
510 | |
511 void SetButtonClickableByMouseButtons(GtkWidget* button, | |
512 bool left, bool middle, bool right) { | |
513 gint button_mask = 0; | |
514 if (left) | |
515 button_mask |= 1 << 1; | |
516 if (middle) | |
517 button_mask |= 1 << 2; | |
518 if (right) | |
519 button_mask |= 1 << 3; | |
520 void* userdata = GINT_TO_POINTER(button_mask); | |
521 | |
522 g_signal_connect(button, "button-press-event", | |
523 G_CALLBACK(OnMouseButtonPressed), userdata); | |
524 g_signal_connect(button, "button-release-event", | |
525 G_CALLBACK(OnMouseButtonReleased), userdata); | |
526 } | |
527 | |
528 void SetButtonTriggersNavigation(GtkWidget* button) { | |
529 SetButtonClickableByMouseButtons(button, true, true, false); | |
530 } | |
531 | |
532 int MirroredLeftPointForRect(GtkWidget* widget, const gfx::Rect& bounds) { | |
533 if (!base::i18n::IsRTL()) | |
534 return bounds.x(); | |
535 | |
536 GtkAllocation allocation; | |
537 gtk_widget_get_allocation(widget, &allocation); | |
538 return allocation.width - bounds.x() - bounds.width(); | |
539 } | |
540 | |
541 int MirroredRightPointForRect(GtkWidget* widget, const gfx::Rect& bounds) { | |
542 if (!base::i18n::IsRTL()) | |
543 return bounds.right(); | |
544 | |
545 GtkAllocation allocation; | |
546 gtk_widget_get_allocation(widget, &allocation); | |
547 return allocation.width - bounds.x(); | |
548 } | |
549 | |
550 int MirroredXCoordinate(GtkWidget* widget, int x) { | |
551 if (base::i18n::IsRTL()) { | |
552 GtkAllocation allocation; | |
553 gtk_widget_get_allocation(widget, &allocation); | |
554 return allocation.width - x; | |
555 } | |
556 return x; | |
557 } | |
558 | |
559 bool WidgetContainsCursor(GtkWidget* widget) { | |
560 gint x = 0; | |
561 gint y = 0; | |
562 gtk_widget_get_pointer(widget, &x, &y); | |
563 return WidgetBounds(widget).Contains(x, y); | |
564 } | |
565 | |
566 void SetDefaultWindowIcon(GtkWindow* window) { | |
567 GtkIconTheme* theme = | |
568 gtk_icon_theme_get_for_screen(gtk_widget_get_screen(GTK_WIDGET(window))); | |
569 | |
570 if (gtk_icon_theme_has_icon(theme, kIconName)) { | |
571 gtk_window_set_default_icon_name(kIconName); | |
572 // Sometimes the WM fails to update the icon when we tell it to. The above | |
573 // line should be enough to update all existing windows, but it can fail, | |
574 // e.g. with Lucid/metacity. The following line seems to fix the common | |
575 // case where the first window created doesn't have an icon. | |
576 gtk_window_set_icon_name(window, kIconName); | |
577 } else { | |
578 GList* icon_list = GetIconList(); | |
579 gtk_window_set_default_icon_list(icon_list); | |
580 // Same logic applies here. | |
581 gtk_window_set_icon_list(window, icon_list); | |
582 g_list_free(icon_list); | |
583 } | |
584 } | |
585 | |
586 void SetWindowIcon(GtkWindow* window, Profile* profile) { | |
587 GList* icon_list = GetIconListWithAvatars(window, profile); | |
588 gtk_window_set_icon_list(window, icon_list); | |
589 g_list_foreach(icon_list, reinterpret_cast<GFunc>(g_object_unref), NULL); | |
590 g_list_free(icon_list); | |
591 } | |
592 | |
593 void SetWindowIcon(GtkWindow* window, Profile* profile, GdkPixbuf* icon) { | |
594 const GdkPixbuf* avatar = GetAvatarIcon(profile); | |
595 if (avatar) AddEmblem(avatar, icon); | |
596 gtk_window_set_icon(window, icon); | |
597 } | |
598 | |
599 GtkWidget* AddButtonToDialog(GtkWidget* dialog, const gchar* text, | |
600 const gchar* stock_id, gint response_id) { | |
601 GtkWidget* button = gtk_button_new_with_label(text); | |
602 gtk_button_set_image(GTK_BUTTON(button), | |
603 gtk_image_new_from_stock(stock_id, | |
604 GTK_ICON_SIZE_BUTTON)); | |
605 gtk_dialog_add_action_widget(GTK_DIALOG(dialog), button, | |
606 response_id); | |
607 return button; | |
608 } | |
609 | |
610 GtkWidget* BuildDialogButton(GtkWidget* dialog, int ids_id, | |
611 const gchar* stock_id) { | |
612 GtkWidget* button = gtk_button_new_with_mnemonic( | |
613 ui::ConvertAcceleratorsFromWindowsStyle( | |
614 l10n_util::GetStringUTF8(ids_id)).c_str()); | |
615 gtk_button_set_image(GTK_BUTTON(button), | |
616 gtk_image_new_from_stock(stock_id, | |
617 GTK_ICON_SIZE_BUTTON)); | |
618 return button; | |
619 } | |
620 | |
621 GtkWidget* CreateEntryImageHBox(GtkWidget* entry, GtkWidget* image) { | |
622 GtkWidget* hbox = gtk_hbox_new(FALSE, ui::kControlSpacing); | |
623 gtk_box_pack_start(GTK_BOX(hbox), entry, TRUE, TRUE, 0); | |
624 gtk_box_pack_start(GTK_BOX(hbox), image, FALSE, FALSE, 0); | |
625 return hbox; | |
626 } | |
627 | |
628 void SetLabelColor(GtkWidget* label, const GdkColor* color) { | |
629 gtk_widget_modify_fg(label, GTK_STATE_NORMAL, color); | |
630 gtk_widget_modify_fg(label, GTK_STATE_ACTIVE, color); | |
631 gtk_widget_modify_fg(label, GTK_STATE_PRELIGHT, color); | |
632 gtk_widget_modify_fg(label, GTK_STATE_INSENSITIVE, color); | |
633 } | |
634 | |
635 GtkWidget* IndentWidget(GtkWidget* content) { | |
636 GtkWidget* content_alignment = gtk_alignment_new(0.0, 0.5, 1.0, 1.0); | |
637 gtk_alignment_set_padding(GTK_ALIGNMENT(content_alignment), 0, 0, | |
638 ui::kGroupIndent, 0); | |
639 gtk_container_add(GTK_CONTAINER(content_alignment), content); | |
640 return content_alignment; | |
641 } | |
642 | |
643 GdkPoint MakeBidiGdkPoint(gint x, gint y, gint width, bool ltr) { | |
644 GdkPoint point = {ltr ? x : width - x, y}; | |
645 return point; | |
646 } | |
647 | |
648 std::string BuildTooltipTitleFor(base::string16 title, const GURL& url) { | |
649 const std::string& url_str = url.possibly_invalid_spec(); | |
650 const std::string& title_str = base::UTF16ToUTF8(title); | |
651 | |
652 std::string truncated_url = base::UTF16ToUTF8(gfx::TruncateString( | |
653 base::UTF8ToUTF16(url_str), kMaxTooltipURLLength)); | |
654 gchar* escaped_url_cstr = g_markup_escape_text(truncated_url.c_str(), | |
655 truncated_url.size()); | |
656 std::string escaped_url(escaped_url_cstr); | |
657 g_free(escaped_url_cstr); | |
658 | |
659 if (url_str == title_str || title.empty()) { | |
660 return escaped_url; | |
661 } else { | |
662 std::string truncated_title = base::UTF16ToUTF8(gfx::TruncateString( | |
663 title, kMaxTooltipTitleLength)); | |
664 gchar* escaped_title_cstr = g_markup_escape_text(truncated_title.c_str(), | |
665 truncated_title.size()); | |
666 std::string escaped_title(escaped_title_cstr); | |
667 g_free(escaped_title_cstr); | |
668 | |
669 if (!escaped_url.empty()) | |
670 return std::string("<b>") + escaped_title + "</b>\n" + escaped_url; | |
671 else | |
672 return std::string("<b>") + escaped_title + "</b>"; | |
673 } | |
674 } | |
675 | |
676 void DrawTextEntryBackground(GtkWidget* offscreen_entry, | |
677 GtkWidget* widget_to_draw_on, | |
678 GdkRectangle* dirty_rec, | |
679 GdkRectangle* rec) { | |
680 GtkStyle* gtk_owned_style = gtk_rc_get_style(offscreen_entry); | |
681 // GTK owns the above and we're going to have to make our own copy of it | |
682 // that we can edit. | |
683 GtkStyle* our_style = gtk_style_copy(gtk_owned_style); | |
684 our_style = gtk_style_attach(our_style, widget_to_draw_on->window); | |
685 | |
686 // TODO(erg): Draw the focus ring if appropriate... | |
687 | |
688 // We're using GTK rendering; draw a GTK entry widget onto the background. | |
689 gtk_paint_shadow(our_style, widget_to_draw_on->window, | |
690 GTK_STATE_NORMAL, GTK_SHADOW_IN, dirty_rec, | |
691 widget_to_draw_on, "entry", | |
692 rec->x, rec->y, rec->width, rec->height); | |
693 | |
694 // Draw the interior background (not all themes draw the entry background | |
695 // above; this is a noop on themes that do). | |
696 gint xborder = our_style->xthickness; | |
697 gint yborder = our_style->ythickness; | |
698 gint width = rec->width - 2 * xborder; | |
699 gint height = rec->height - 2 * yborder; | |
700 if (width > 0 && height > 0) { | |
701 gtk_paint_flat_box(our_style, widget_to_draw_on->window, | |
702 GTK_STATE_NORMAL, GTK_SHADOW_NONE, dirty_rec, | |
703 widget_to_draw_on, "entry_bg", | |
704 rec->x + xborder, rec->y + yborder, | |
705 width, height); | |
706 } | |
707 | |
708 gtk_style_detach(our_style); | |
709 g_object_unref(our_style); | |
710 } | |
711 | |
712 void SetLayoutText(PangoLayout* layout, const base::string16& text) { | |
713 // Pango is really easy to overflow and send into a computational death | |
714 // spiral that can corrupt the screen. Assume that we'll never have more than | |
715 // 2000 characters, which should be a safe assumption until we all get robot | |
716 // eyes. http://crbug.com/66576 | |
717 std::string text_utf8 = base::UTF16ToUTF8(text); | |
718 if (text_utf8.length() > 2000) | |
719 text_utf8 = text_utf8.substr(0, 2000); | |
720 | |
721 pango_layout_set_text(layout, text_utf8.data(), text_utf8.length()); | |
722 } | |
723 | |
724 void DrawThemedToolbarBackground(GtkWidget* widget, | |
725 cairo_t* cr, | |
726 GdkEventExpose* event, | |
727 const gfx::Point& tabstrip_origin, | |
728 GtkThemeService* theme_service) { | |
729 // Fill the entire region with the toolbar color. | |
730 GdkColor color = theme_service->GetGdkColor( | |
731 ThemeProperties::COLOR_TOOLBAR); | |
732 gdk_cairo_set_source_color(cr, &color); | |
733 cairo_fill(cr); | |
734 | |
735 // The toolbar is supposed to blend in with the active tab, so we have to pass | |
736 // coordinates for the IDR_THEME_TOOLBAR bitmap relative to the top of the | |
737 // tab strip. | |
738 const gfx::Image background = | |
739 theme_service->GetImageNamed(IDR_THEME_TOOLBAR); | |
740 background.ToCairo()->SetSource(cr, widget, | |
741 tabstrip_origin.x(), tabstrip_origin.y()); | |
742 // We tile the toolbar background in both directions. | |
743 cairo_pattern_set_extend(cairo_get_source(cr), CAIRO_EXTEND_REPEAT); | |
744 cairo_rectangle(cr, | |
745 tabstrip_origin.x(), | |
746 tabstrip_origin.y(), | |
747 event->area.x + event->area.width - tabstrip_origin.x(), | |
748 event->area.y + event->area.height - tabstrip_origin.y()); | |
749 cairo_fill(cr); | |
750 } | |
751 | |
752 void DrawFullImage(cairo_t* cr, | |
753 GtkWidget* widget, | |
754 const gfx::Image& image, | |
755 gint dest_x, | |
756 gint dest_y) { | |
757 gfx::CairoCachedSurface* surface = image.ToCairo(); | |
758 surface->SetSource(cr, widget, dest_x, dest_y); | |
759 cairo_pattern_set_extend(cairo_get_source(cr), CAIRO_EXTEND_REPEAT); | |
760 cairo_rectangle(cr, dest_x, dest_y, surface->Width(), surface->Height()); | |
761 cairo_fill(cr); | |
762 } | |
763 | |
764 GdkColor AverageColors(GdkColor color_one, GdkColor color_two) { | |
765 GdkColor average_color; | |
766 average_color.pixel = 0; | |
767 average_color.red = (color_one.red + color_two.red) / 2; | |
768 average_color.green = (color_one.green + color_two.green) / 2; | |
769 average_color.blue = (color_one.blue + color_two.blue) / 2; | |
770 return average_color; | |
771 } | |
772 | |
773 void SetAlwaysShowImage(GtkWidget* image_menu_item) { | |
774 gtk_image_menu_item_set_always_show_image( | |
775 GTK_IMAGE_MENU_ITEM(image_menu_item), TRUE); | |
776 } | |
777 | |
778 gfx::Rect GetWidgetRectRelativeToToplevel(GtkWidget* widget) { | |
779 DCHECK(gtk_widget_get_realized(widget)); | |
780 | |
781 GtkWidget* toplevel = gtk_widget_get_toplevel(widget); | |
782 DCHECK(toplevel); | |
783 DCHECK(gtk_widget_get_realized(toplevel)); | |
784 | |
785 gint x = 0, y = 0; | |
786 gtk_widget_translate_coordinates(widget, | |
787 toplevel, | |
788 0, 0, | |
789 &x, &y); | |
790 | |
791 GtkAllocation allocation; | |
792 gtk_widget_get_allocation(widget, &allocation); | |
793 return gfx::Rect(x, y, allocation.width, allocation.height); | |
794 } | |
795 | |
796 void SuppressDefaultPainting(GtkWidget* container) { | |
797 g_signal_connect(container, "expose-event", | |
798 G_CALLBACK(PaintNoBackground), NULL); | |
799 } | |
800 | |
801 bool GrabAllInput(GtkWidget* widget) { | |
802 guint time = gtk_get_current_event_time(); | |
803 | |
804 if (!gtk_widget_get_visible(widget)) | |
805 return false; | |
806 | |
807 GdkWindow* gdk_window = gtk_widget_get_window(widget); | |
808 if (gdk_pointer_grab(gdk_window, | |
809 TRUE, | |
810 GdkEventMask(GDK_BUTTON_PRESS_MASK | | |
811 GDK_BUTTON_RELEASE_MASK | | |
812 GDK_ENTER_NOTIFY_MASK | | |
813 GDK_LEAVE_NOTIFY_MASK | | |
814 GDK_POINTER_MOTION_MASK), | |
815 NULL, NULL, time) != 0) { | |
816 return false; | |
817 } | |
818 | |
819 if (gdk_keyboard_grab(gdk_window, TRUE, time) != 0) { | |
820 gdk_display_pointer_ungrab(gdk_drawable_get_display(gdk_window), time); | |
821 return false; | |
822 } | |
823 | |
824 gtk_grab_add(widget); | |
825 return true; | |
826 } | |
827 | |
828 gfx::Rect WidgetBounds(GtkWidget* widget) { | |
829 // To quote the gtk docs: | |
830 // | |
831 // Widget coordinates are a bit odd; for historical reasons, they are | |
832 // defined as widget->window coordinates for widgets that are not | |
833 // GTK_NO_WINDOW widgets, and are relative to allocation.x, allocation.y | |
834 // for widgets that are GTK_NO_WINDOW widgets. | |
835 // | |
836 // So the base is always (0,0). | |
837 GtkAllocation allocation; | |
838 gtk_widget_get_allocation(widget, &allocation); | |
839 return gfx::Rect(0, 0, allocation.width, allocation.height); | |
840 } | |
841 | |
842 void SetWMLastUserActionTime(GtkWindow* window) { | |
843 gdk_x11_window_set_user_time(gtk_widget_get_window(GTK_WIDGET(window)), | |
844 XTimeNow()); | |
845 } | |
846 | |
847 guint32 XTimeNow() { | |
848 struct timespec ts; | |
849 clock_gettime(CLOCK_MONOTONIC, &ts); | |
850 return ts.tv_sec * 1000 + ts.tv_nsec / 1000000; | |
851 } | |
852 | |
853 bool URLFromPrimarySelection(Profile* profile, GURL* url) { | |
854 GtkClipboard* clipboard = gtk_clipboard_get(GDK_SELECTION_PRIMARY); | |
855 DCHECK(clipboard); | |
856 gchar* selection_text = gtk_clipboard_wait_for_text(clipboard); | |
857 if (!selection_text) | |
858 return false; | |
859 | |
860 // Use autocomplete to clean up the text, going so far as to turn it into | |
861 // a search query if necessary. | |
862 AutocompleteMatch match; | |
863 AutocompleteClassifierFactory::GetForProfile(profile)->Classify( | |
864 base::UTF8ToUTF16(selection_text), false, false, | |
865 AutocompleteInput::INVALID_SPEC, &match, NULL); | |
866 g_free(selection_text); | |
867 if (!match.destination_url.is_valid()) | |
868 return false; | |
869 | |
870 *url = match.destination_url; | |
871 return true; | |
872 } | |
873 | |
874 bool AddWindowAlphaChannel(GtkWidget* window) { | |
875 GdkScreen* screen = gtk_widget_get_screen(window); | |
876 GdkColormap* rgba = gdk_screen_get_rgba_colormap(screen); | |
877 if (rgba) | |
878 gtk_widget_set_colormap(window, rgba); | |
879 | |
880 return rgba; | |
881 } | |
882 | |
883 void GetTextColors(GdkColor* normal_base, | |
884 GdkColor* selected_base, | |
885 GdkColor* normal_text, | |
886 GdkColor* selected_text) { | |
887 GtkWidget* fake_entry = gtk_entry_new(); | |
888 GtkStyle* style = gtk_rc_get_style(fake_entry); | |
889 | |
890 if (normal_base) | |
891 *normal_base = style->base[GTK_STATE_NORMAL]; | |
892 if (selected_base) | |
893 *selected_base = style->base[GTK_STATE_SELECTED]; | |
894 if (normal_text) | |
895 *normal_text = style->text[GTK_STATE_NORMAL]; | |
896 if (selected_text) | |
897 *selected_text = style->text[GTK_STATE_SELECTED]; | |
898 | |
899 g_object_ref_sink(fake_entry); | |
900 g_object_unref(fake_entry); | |
901 } | |
902 | |
903 void ShowDialog(GtkWidget* dialog) { | |
904 gtk_widget_show_all(dialog); | |
905 } | |
906 | |
907 void ShowDialogWithLocalizedSize(GtkWidget* dialog, | |
908 int width_id, | |
909 int height_id, | |
910 bool resizeable) { | |
911 gtk_widget_realize(dialog); | |
912 SetWindowSizeFromResources(GTK_WINDOW(dialog), | |
913 width_id, | |
914 height_id, | |
915 resizeable); | |
916 gtk_widget_show_all(dialog); | |
917 } | |
918 | |
919 void ShowDialogWithMinLocalizedWidth(GtkWidget* dialog, | |
920 int width_id) { | |
921 gtk_widget_show_all(dialog); | |
922 | |
923 // Suggest a minimum size. | |
924 gint width; | |
925 GtkRequisition req; | |
926 gtk_widget_size_request(dialog, &req); | |
927 gtk_util::GetWidgetSizeFromResources(dialog, width_id, 0, &width, NULL); | |
928 if (width > req.width) | |
929 gtk_widget_set_size_request(dialog, width, -1); | |
930 } | |
931 | |
932 void PresentWindow(GtkWidget* window, int timestamp) { | |
933 if (timestamp) | |
934 gtk_window_present_with_time(GTK_WINDOW(window), timestamp); | |
935 else | |
936 gtk_window_present(GTK_WINDOW(window)); | |
937 } | |
938 | |
939 gfx::Rect GetDialogBounds(GtkWidget* dialog) { | |
940 gint x = 0, y = 0, width = 1, height = 1; | |
941 gtk_window_get_position(GTK_WINDOW(dialog), &x, &y); | |
942 gtk_window_get_size(GTK_WINDOW(dialog), &width, &height); | |
943 | |
944 return gfx::Rect(x, y, width, height); | |
945 } | |
946 | |
947 base::string16 GetStockPreferencesMenuLabel() { | |
948 GtkStockItem stock_item; | |
949 base::string16 preferences; | |
950 if (gtk_stock_lookup(GTK_STOCK_PREFERENCES, &stock_item)) { | |
951 const base::char16 kUnderscore[] = { '_', 0 }; | |
952 base::RemoveChars(base::UTF8ToUTF16(stock_item.label), | |
953 kUnderscore, &preferences); | |
954 } | |
955 return preferences; | |
956 } | |
957 | |
958 bool IsWidgetAncestryVisible(GtkWidget* widget) { | |
959 GtkWidget* parent = widget; | |
960 while (parent && gtk_widget_get_visible(parent)) | |
961 parent = gtk_widget_get_parent(parent); | |
962 return !parent; | |
963 } | |
964 | |
965 void SetLabelWidth(GtkWidget* label, int pixel_width) { | |
966 gtk_label_set_line_wrap(GTK_LABEL(label), TRUE); | |
967 gtk_misc_set_alignment(GTK_MISC(label), 0, 0.5); | |
968 | |
969 // Do the simple thing in LTR because the bug only affects right-aligned | |
970 // text. Also, when using the workaround, the label tries to maintain | |
971 // uniform line-length, which we don't really want. | |
972 if (gtk_widget_get_direction(label) == GTK_TEXT_DIR_LTR) { | |
973 gtk_widget_set_size_request(label, pixel_width, -1); | |
974 } else { | |
975 // The label has to be realized before we can adjust its width. | |
976 if (gtk_widget_get_realized(label)) { | |
977 OnLabelRealize(label, GINT_TO_POINTER(pixel_width)); | |
978 } else { | |
979 g_signal_connect(label, "realize", G_CALLBACK(OnLabelRealize), | |
980 GINT_TO_POINTER(pixel_width)); | |
981 } | |
982 } | |
983 } | |
984 | |
985 void InitLabelSizeRequestAndEllipsizeMode(GtkWidget* label) { | |
986 GtkRequisition size; | |
987 gtk_label_set_ellipsize(GTK_LABEL(label), PANGO_ELLIPSIZE_NONE); | |
988 gtk_widget_set_size_request(label, -1, -1); | |
989 gtk_widget_size_request(label, &size); | |
990 gtk_widget_set_size_request(label, size.width, size.height); | |
991 gtk_label_set_ellipsize(GTK_LABEL(label), PANGO_ELLIPSIZE_END); | |
992 } | |
993 | |
994 void ApplyMessageDialogQuirks(GtkWidget* dialog) { | |
995 if (gtk_window_get_modal(GTK_WINDOW(dialog))) { | |
996 // Work around a KDE 3 window manager bug. | |
997 scoped_ptr<base::Environment> env(base::Environment::Create()); | |
998 if (base::nix::DESKTOP_ENVIRONMENT_KDE3 == | |
999 base::nix::GetDesktopEnvironment(env.get())) | |
1000 gtk_window_set_skip_taskbar_hint(GTK_WINDOW(dialog), FALSE); | |
1001 } | |
1002 } | |
1003 | |
1004 } // namespace gtk_util | |
OLD | NEW |