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.
5 #include "chrome/browser/ui/gtk/gtk_util.h"
7 #include <cairo/cairo.h>
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_match.h"
22 #include "chrome/browser/browser_process.h"
23 #include "chrome/browser/profiles/profile.h"
24 #include "chrome/browser/profiles/profile_info_cache.h"
25 #include "chrome/browser/profiles/profile_manager.h"
26 #include "chrome/browser/profiles/profiles_state.h"
27 #include "chrome/browser/themes/theme_properties.h"
28 #include "chrome/browser/ui/browser.h"
29 #include "chrome/browser/ui/browser_iterator.h"
30 #include "chrome/browser/ui/browser_list.h"
31 #include "chrome/browser/ui/browser_window.h"
32 #include "chrome/browser/ui/gtk/browser_window_gtk.h"
33 #include "chrome/browser/ui/gtk/gtk_theme_service.h"
34 #include "chrome/browser/ui/host_desktop.h"
35 #include "grit/chrome_unscaled_resources.h"
36 #include "grit/theme_resources.h"
37 #include "ui/base/accelerators/menu_label_accelerator_util_linux.h"
38 #include "ui/base/gtk/gtk_hig_constants.h"
39 #include "ui/base/gtk/gtk_screen_util.h"
40 #include "ui/base/l10n/l10n_util.h"
41 #include "ui/base/resource/resource_bundle.h"
42 #include "ui/base/x/x11_util.h"
43 #include "ui/gfx/gtk_compat.h"
44 #include "ui/gfx/image/cairo_cached_surface.h"
45 #include "ui/gfx/image/image.h"
46 #include "ui/gfx/pango_util.h"
47 #include "ui/gfx/text_elider.h"
50 // These conflict with base/tracked_objects.h, so need to come last.
51 #include <gdk/gdkx.h> // NOLINT
55 #if defined(GOOGLE_CHROME_BUILD)
56 static const char* kIconName
= "google-chrome";
58 static const char* kIconName
= "chromium-browser";
61 const char kBoldLabelMarkup
[] = "<span weight='bold'>%s</span>";
63 // Max size of each component of the button tooltips.
64 const size_t kMaxTooltipTitleLength
= 100;
65 const size_t kMaxTooltipURLLength
= 400;
67 // Callback used in RemoveAllChildren.
68 void RemoveWidget(GtkWidget
* widget
, gpointer container
) {
69 gtk_container_remove(GTK_CONTAINER(container
), widget
);
72 // These two functions are copped almost directly from gtk core. The only
73 // difference is that they accept middle clicks.
74 gboolean
OnMouseButtonPressed(GtkWidget
* widget
, GdkEventButton
* event
,
76 if (event
->type
== GDK_BUTTON_PRESS
) {
77 if (gtk_button_get_focus_on_click(GTK_BUTTON(widget
)) &&
78 !gtk_widget_has_focus(widget
)) {
79 gtk_widget_grab_focus(widget
);
82 gint button_mask
= GPOINTER_TO_INT(userdata
);
83 if (button_mask
& (1 << event
->button
))
84 gtk_button_pressed(GTK_BUTTON(widget
));
90 gboolean
OnMouseButtonReleased(GtkWidget
* widget
, GdkEventButton
* event
,
92 gint button_mask
= GPOINTER_TO_INT(userdata
);
93 if (button_mask
&& (1 << event
->button
))
94 gtk_button_released(GTK_BUTTON(widget
));
99 // Returns the approximate number of characters that can horizontally fit in
100 // |pixel_width| pixels.
101 int GetCharacterWidthForPixels(GtkWidget
* widget
, int pixel_width
) {
102 DCHECK(gtk_widget_get_realized(widget
))
103 << " widget must be realized to compute font metrics correctly";
105 PangoContext
* context
= gtk_widget_create_pango_context(widget
);
106 GtkStyle
* style
= gtk_widget_get_style(widget
);
107 PangoFontMetrics
* metrics
= pango_context_get_metrics(context
,
108 style
->font_desc
, pango_context_get_language(context
));
110 // This technique (max of char and digit widths) matches the code in
112 int char_width
= pixel_width
* PANGO_SCALE
/
113 std::max(pango_font_metrics_get_approximate_char_width(metrics
),
114 pango_font_metrics_get_approximate_digit_width(metrics
));
116 pango_font_metrics_unref(metrics
);
117 g_object_unref(context
);
122 void OnLabelRealize(GtkWidget
* label
, gpointer pixel_width
) {
123 gtk_label_set_width_chars(
125 GetCharacterWidthForPixels(label
, GPOINTER_TO_INT(pixel_width
)));
128 // Ownership of |icon_list| is passed to the caller.
129 GList
* GetIconList() {
130 ui::ResourceBundle
& rb
= ui::ResourceBundle::GetSharedInstance();
131 GList
* icon_list
= NULL
;
132 icon_list
= g_list_append(icon_list
,
133 rb
.GetNativeImageNamed(IDR_PRODUCT_LOGO_32
).ToGdkPixbuf());
134 icon_list
= g_list_append(icon_list
,
135 rb
.GetNativeImageNamed(IDR_PRODUCT_LOGO_16
).ToGdkPixbuf());
139 // Returns the avatar icon for |profile|.
141 // Returns NULL if there is only one profile; always returns an icon for
142 // Incognito profiles.
144 // The returned pixbuf must not be unreferenced or freed because it's owned by
145 // either the resource bundle or the profile info cache.
146 GdkPixbuf
* GetAvatarIcon(Profile
* profile
) {
147 if (profile
->IsOffTheRecord()) {
148 ui::ResourceBundle
& rb
= ui::ResourceBundle::GetSharedInstance();
149 return rb
.GetNativeImageNamed(IDR_OTR_ICON
).ToGdkPixbuf();
152 const ProfileInfoCache
& cache
=
153 g_browser_process
->profile_manager()->GetProfileInfoCache();
155 if (!profiles::IsMultipleProfilesEnabled() ||
156 cache
.GetNumberOfProfiles() < 2)
159 const size_t index
= cache
.GetIndexOfProfileWithPath(profile
->GetPath());
161 return (index
!= std::string::npos
?
162 cache
.GetAvatarIconOfProfileAtIndex(index
).ToGdkPixbuf() :
163 static_cast<GdkPixbuf
*>(NULL
));
166 // Gets the Chrome product icon.
168 // If it doesn't find the icon in |theme|, it looks among the icons packaged
171 // Supported values of |size| are 16, 32, and 64. If the Chrome icon is found
172 // in |theme|, the returned icon may not be of the requested size if |size|
173 // has an unsupported value (GTK might scale it). If the Chrome icon is not
174 // found in |theme|, and |size| has an unsupported value, the program will be
175 // aborted with CHECK(false).
177 // The caller is responsible for calling g_object_unref() on the returned
179 GdkPixbuf
* GetChromeIcon(GtkIconTheme
* theme
, const int size
) {
180 if (gtk_icon_theme_has_icon(theme
, kIconName
)) {
182 gtk_icon_theme_load_icon(theme
,
185 static_cast<GtkIconLookupFlags
>(0),
187 GdkPixbuf
* icon_copy
= gdk_pixbuf_copy(icon
);
188 g_object_unref(icon
);
192 ui::ResourceBundle
& rb
= ui::ResourceBundle::GetSharedInstance();
196 case 16: id
= IDR_PRODUCT_LOGO_16
; break;
197 case 32: id
= IDR_PRODUCT_LOGO_32
; break;
198 case 64: id
= IDR_PRODUCT_LOGO_64
; break;
199 default: CHECK(false); break;
202 return gdk_pixbuf_copy(rb
.GetNativeImageNamed(id
).ToGdkPixbuf());
205 // Adds |emblem| to the bottom-right corner of |icon|.
207 // Taking the ceiling of the scaled destination rect's dimensions (|dest_w|
208 // and |dest_h|) because, if the destination rect is larger than the scaled
209 // emblem, gdk_pixbuf_composite() will replicate the edge pixels of the emblem
210 // to fill the gap, which is better than a cropped emblem, I think.
211 void AddEmblem(const GdkPixbuf
* emblem
, GdkPixbuf
* icon
) {
212 const int iw
= gdk_pixbuf_get_width(icon
);
213 const int ih
= gdk_pixbuf_get_height(icon
);
214 const int ew
= gdk_pixbuf_get_width(emblem
);
215 const int eh
= gdk_pixbuf_get_height(emblem
);
217 const double emblem_scale
=
218 (static_cast<double>(ih
) / static_cast<double>(eh
)) * 0.5;
219 const int dest_w
= ::ceil(ew
* emblem_scale
);
220 const int dest_h
= ::ceil(eh
* emblem_scale
);
221 const int x
= iw
- dest_w
; // Used for offset_x and dest_x.
222 const int y
= ih
- dest_h
; // Used for offset_y and dest_y.
224 gdk_pixbuf_composite(emblem
, icon
,
228 emblem_scale
, emblem_scale
,
229 GDK_INTERP_BILINEAR
, 255);
232 // Returns a list containing Chrome icons of various sizes emblemed with the
233 // |profile|'s avatar.
235 // If there is only one profile, no emblem is added, but icons for Incognito
236 // profiles will always get the Incognito emblem.
238 // The caller owns the list and all the icons it contains will have had their
239 // reference counts incremented. Therefore the caller should unreference each
240 // element before freeing the list.
241 GList
* GetIconListWithAvatars(GtkWindow
* window
, Profile
* profile
) {
242 GtkIconTheme
* theme
=
243 gtk_icon_theme_get_for_screen(gtk_widget_get_screen(GTK_WIDGET(window
)));
245 GdkPixbuf
* icon_16
= GetChromeIcon(theme
, 16);
246 GdkPixbuf
* icon_32
= GetChromeIcon(theme
, 32);
247 GdkPixbuf
* icon_64
= GetChromeIcon(theme
, 64);
249 const GdkPixbuf
* avatar
= GetAvatarIcon(profile
);
251 AddEmblem(avatar
, icon_16
);
252 AddEmblem(avatar
, icon_32
);
253 AddEmblem(avatar
, icon_64
);
256 GList
* icon_list
= NULL
;
257 icon_list
= g_list_append(icon_list
, icon_64
);
258 icon_list
= g_list_append(icon_list
, icon_32
);
259 icon_list
= g_list_append(icon_list
, icon_16
);
264 // Expose event handler for a container that simply suppresses the default
265 // drawing and propagates the expose event to the container's children.
266 gboolean
PaintNoBackground(GtkWidget
* widget
,
267 GdkEventExpose
* event
,
269 GList
* children
= gtk_container_get_children(GTK_CONTAINER(widget
));
270 for (GList
* item
= children
; item
; item
= item
->next
) {
271 gtk_container_propagate_expose(GTK_CONTAINER(widget
),
272 GTK_WIDGET(item
->data
),
275 g_list_free(children
);
284 GtkWidget
* CreateLabeledControlsGroup(std::vector
<GtkWidget
*>* labels
,
285 const char* text
, ...) {
288 GtkWidget
* table
= gtk_table_new(0, 2, FALSE
);
289 gtk_table_set_col_spacing(GTK_TABLE(table
), 0, ui::kLabelSpacing
);
290 gtk_table_set_row_spacings(GTK_TABLE(table
), ui::kControlSpacing
);
292 for (guint row
= 0; text
; ++row
) {
293 gtk_table_resize(GTK_TABLE(table
), row
+ 1, 2);
294 GtkWidget
* control
= va_arg(ap
, GtkWidget
*);
295 GtkWidget
* label
= gtk_label_new(text
);
296 gtk_misc_set_alignment(GTK_MISC(label
), 0, 0.5);
298 labels
->push_back(label
);
300 gtk_table_attach(GTK_TABLE(table
), label
,
304 gtk_table_attach_defaults(GTK_TABLE(table
), control
,
306 text
= va_arg(ap
, const char*);
313 GtkWidget
* CreateGtkBorderBin(GtkWidget
* child
, const GdkColor
* color
,
314 int top
, int bottom
, int left
, int right
) {
315 // Use a GtkEventBox to get the background painted. However, we can't just
316 // use a container border, since it won't paint there. Use an alignment
317 // inside to get the sizes exactly of how we want the border painted.
318 GtkWidget
* ebox
= gtk_event_box_new();
320 gtk_widget_modify_bg(ebox
, GTK_STATE_NORMAL
, color
);
321 GtkWidget
* alignment
= gtk_alignment_new(0.0, 0.0, 1.0, 1.0);
322 gtk_alignment_set_padding(GTK_ALIGNMENT(alignment
), top
, bottom
, left
, right
);
323 gtk_container_add(GTK_CONTAINER(alignment
), child
);
324 gtk_container_add(GTK_CONTAINER(ebox
), alignment
);
328 GtkWidget
* LeftAlignMisc(GtkWidget
* misc
) {
329 gtk_misc_set_alignment(GTK_MISC(misc
), 0, 0.5);
333 GtkWidget
* CreateBoldLabel(const std::string
& text
) {
334 GtkWidget
* label
= gtk_label_new(NULL
);
335 char* markup
= g_markup_printf_escaped(kBoldLabelMarkup
, text
.c_str());
336 gtk_label_set_markup(GTK_LABEL(label
), markup
);
339 return LeftAlignMisc(label
);
342 void GetWidgetSizeFromCharacters(
343 GtkWidget
* widget
, double width_chars
, double height_lines
,
344 int* width
, int* height
) {
345 DCHECK(gtk_widget_get_realized(widget
))
346 << " widget must be realized to compute font metrics correctly";
347 PangoContext
* context
= gtk_widget_create_pango_context(widget
);
348 GtkStyle
* style
= gtk_widget_get_style(widget
);
349 PangoFontMetrics
* metrics
= pango_context_get_metrics(context
,
350 style
->font_desc
, pango_context_get_language(context
));
352 *width
= static_cast<int>(
353 pango_font_metrics_get_approximate_char_width(metrics
) *
354 width_chars
/ PANGO_SCALE
);
357 *height
= static_cast<int>(
358 (pango_font_metrics_get_ascent(metrics
) +
359 pango_font_metrics_get_descent(metrics
)) *
360 height_lines
/ PANGO_SCALE
);
362 pango_font_metrics_unref(metrics
);
363 g_object_unref(context
);
366 void GetWidgetSizeFromResources(
367 GtkWidget
* widget
, int width_chars
, int height_lines
,
368 int* width
, int* height
) {
369 DCHECK(gtk_widget_get_realized(widget
))
370 << " widget must be realized to compute font metrics correctly";
374 base::StringToDouble(l10n_util::GetStringUTF8(width_chars
), &chars
);
378 base::StringToDouble(l10n_util::GetStringUTF8(height_lines
), &lines
);
380 GetWidgetSizeFromCharacters(widget
, chars
, lines
, width
, height
);
383 void SetWindowSizeFromResources(GtkWindow
* window
,
384 int width_id
, int height_id
, bool resizable
) {
387 gtk_util::GetWidgetSizeFromResources(GTK_WIDGET(window
), width_id
, height_id
,
388 (width_id
!= -1) ? &width
: NULL
,
389 (height_id
!= -1) ? &height
: NULL
);
392 gtk_window_set_default_size(window
, width
, height
);
394 // For a non-resizable window, GTK tries to snap the window size
395 // to the minimum size around the content. We use the sizes in
396 // the resources to set *minimum* window size to allow windows
397 // with long titles to be wide enough to display their titles.
399 // But if GTK wants to make the window *wider* due to very wide
400 // controls, we should allow that too, so be careful to pick the
401 // wider of the resources size and the natural window size.
403 gtk_widget_show_all(gtk_bin_get_child(GTK_BIN(window
)));
404 GtkRequisition requisition
;
405 gtk_widget_size_request(GTK_WIDGET(window
), &requisition
);
406 gtk_widget_set_size_request(
408 width
== -1 ? -1 : std::max(width
, requisition
.width
),
409 height
== -1 ? -1 : std::max(height
, requisition
.height
));
411 gtk_window_set_resizable(window
, resizable
? TRUE
: FALSE
);
414 void MakeAppModalWindowGroup() {
415 // Older versions of GTK+ don't give us gtk_window_group_list() which is what
416 // we need to add current non-browser modal dialogs to the list. If
417 // we have 2.14+ we can do things the correct way.
418 GtkWindowGroup
* window_group
= gtk_window_group_new();
419 for (chrome::BrowserIterator it
; !it
.done(); it
.Next()) {
420 // List all windows in this current group
421 GtkWindowGroup
* old_group
=
422 gtk_window_get_group((*it
)->window()->GetNativeWindow());
424 GList
* all_windows
= gtk_window_group_list_windows(old_group
);
425 for (GList
* window
= all_windows
; window
; window
= window
->next
) {
426 gtk_window_group_add_window(window_group
, GTK_WINDOW(window
->data
));
428 g_list_free(all_windows
);
430 g_object_unref(window_group
);
433 void AppModalDismissedUngroupWindows() {
434 // GTK only has the native desktop.
435 const BrowserList
* native_browser_list
=
436 BrowserList::GetInstance(chrome::HOST_DESKTOP_TYPE_NATIVE
);
437 if (!native_browser_list
->empty()) {
438 std::vector
<GtkWindow
*> transient_windows
;
440 // All windows should be part of one big modal group right now.
441 GtkWindowGroup
* window_group
= gtk_window_get_group(
442 native_browser_list
->get(0)->window()->GetNativeWindow());
443 GList
* windows
= gtk_window_group_list_windows(window_group
);
445 for (GList
* item
= windows
; item
; item
= item
->next
) {
446 GtkWindow
* window
= GTK_WINDOW(item
->data
);
447 GtkWindow
* transient_for
= gtk_window_get_transient_for(window
);
449 transient_windows
.push_back(window
);
451 GtkWindowGroup
* window_group
= gtk_window_group_new();
452 gtk_window_group_add_window(window_group
, window
);
453 g_object_unref(window_group
);
457 // Put each transient window in the same group as its transient parent.
458 for (std::vector
<GtkWindow
*>::iterator it
= transient_windows
.begin();
459 it
!= transient_windows
.end(); ++it
) {
460 GtkWindow
* transient_parent
= gtk_window_get_transient_for(*it
);
461 GtkWindowGroup
* group
= gtk_window_get_group(transient_parent
);
462 gtk_window_group_add_window(group
, *it
);
464 g_list_free(windows
);
468 void RemoveAllChildren(GtkWidget
* container
) {
469 gtk_container_foreach(GTK_CONTAINER(container
), RemoveWidget
, container
);
472 void ForceFontSizePixels(GtkWidget
* widget
, double size_pixels
) {
473 gfx::ScopedPangoFontDescription
font_desc(pango_font_description_new());
474 // pango_font_description_set_absolute_size sets the font size in device
475 // units, which for us is pixels.
476 pango_font_description_set_absolute_size(font_desc
.get(),
477 PANGO_SCALE
* size_pixels
);
478 gtk_widget_modify_font(widget
, font_desc
.get());
481 void UndoForceFontSize(GtkWidget
* widget
) {
482 gtk_widget_modify_font(widget
, NULL
);
485 gfx::Size
GetWidgetSize(GtkWidget
* widget
) {
487 gtk_widget_size_request(widget
, &size
);
488 return gfx::Size(size
.width
, size
.height
);
491 void ConvertWidgetPointToScreen(GtkWidget
* widget
, gfx::Point
* p
) {
495 *p
+= ui::GetWidgetScreenOffset(widget
);
498 GtkWidget
* CenterWidgetInHBox(GtkWidget
* hbox
, GtkWidget
* widget
,
499 bool pack_at_end
, int padding
) {
500 GtkWidget
* centering_vbox
= gtk_vbox_new(FALSE
, 0);
501 gtk_box_pack_start(GTK_BOX(centering_vbox
), widget
, TRUE
, FALSE
, 0);
503 gtk_box_pack_end(GTK_BOX(hbox
), centering_vbox
, FALSE
, FALSE
, padding
);
505 gtk_box_pack_start(GTK_BOX(hbox
), centering_vbox
, FALSE
, FALSE
, padding
);
507 return centering_vbox
;
510 void SetButtonClickableByMouseButtons(GtkWidget
* button
,
511 bool left
, bool middle
, bool right
) {
512 gint button_mask
= 0;
514 button_mask
|= 1 << 1;
516 button_mask
|= 1 << 2;
518 button_mask
|= 1 << 3;
519 void* userdata
= GINT_TO_POINTER(button_mask
);
521 g_signal_connect(button
, "button-press-event",
522 G_CALLBACK(OnMouseButtonPressed
), userdata
);
523 g_signal_connect(button
, "button-release-event",
524 G_CALLBACK(OnMouseButtonReleased
), userdata
);
527 void SetButtonTriggersNavigation(GtkWidget
* button
) {
528 SetButtonClickableByMouseButtons(button
, true, true, false);
531 int MirroredLeftPointForRect(GtkWidget
* widget
, const gfx::Rect
& bounds
) {
532 if (!base::i18n::IsRTL())
535 GtkAllocation allocation
;
536 gtk_widget_get_allocation(widget
, &allocation
);
537 return allocation
.width
- bounds
.x() - bounds
.width();
540 int MirroredRightPointForRect(GtkWidget
* widget
, const gfx::Rect
& bounds
) {
541 if (!base::i18n::IsRTL())
542 return bounds
.right();
544 GtkAllocation allocation
;
545 gtk_widget_get_allocation(widget
, &allocation
);
546 return allocation
.width
- bounds
.x();
549 int MirroredXCoordinate(GtkWidget
* widget
, int x
) {
550 if (base::i18n::IsRTL()) {
551 GtkAllocation allocation
;
552 gtk_widget_get_allocation(widget
, &allocation
);
553 return allocation
.width
- x
;
558 bool WidgetContainsCursor(GtkWidget
* widget
) {
561 gtk_widget_get_pointer(widget
, &x
, &y
);
562 return WidgetBounds(widget
).Contains(x
, y
);
565 void SetDefaultWindowIcon(GtkWindow
* window
) {
566 GtkIconTheme
* theme
=
567 gtk_icon_theme_get_for_screen(gtk_widget_get_screen(GTK_WIDGET(window
)));
569 if (gtk_icon_theme_has_icon(theme
, kIconName
)) {
570 gtk_window_set_default_icon_name(kIconName
);
571 // Sometimes the WM fails to update the icon when we tell it to. The above
572 // line should be enough to update all existing windows, but it can fail,
573 // e.g. with Lucid/metacity. The following line seems to fix the common
574 // case where the first window created doesn't have an icon.
575 gtk_window_set_icon_name(window
, kIconName
);
577 GList
* icon_list
= GetIconList();
578 gtk_window_set_default_icon_list(icon_list
);
579 // Same logic applies here.
580 gtk_window_set_icon_list(window
, icon_list
);
581 g_list_free(icon_list
);
585 void SetWindowIcon(GtkWindow
* window
, Profile
* profile
) {
586 GList
* icon_list
= GetIconListWithAvatars(window
, profile
);
587 gtk_window_set_icon_list(window
, icon_list
);
588 g_list_foreach(icon_list
, reinterpret_cast<GFunc
>(g_object_unref
), NULL
);
589 g_list_free(icon_list
);
592 void SetWindowIcon(GtkWindow
* window
, Profile
* profile
, GdkPixbuf
* icon
) {
593 const GdkPixbuf
* avatar
= GetAvatarIcon(profile
);
594 if (avatar
) AddEmblem(avatar
, icon
);
595 gtk_window_set_icon(window
, icon
);
598 GtkWidget
* AddButtonToDialog(GtkWidget
* dialog
, const gchar
* text
,
599 const gchar
* stock_id
, gint response_id
) {
600 GtkWidget
* button
= gtk_button_new_with_label(text
);
601 gtk_button_set_image(GTK_BUTTON(button
),
602 gtk_image_new_from_stock(stock_id
,
603 GTK_ICON_SIZE_BUTTON
));
604 gtk_dialog_add_action_widget(GTK_DIALOG(dialog
), button
,
609 GtkWidget
* BuildDialogButton(GtkWidget
* dialog
, int ids_id
,
610 const gchar
* stock_id
) {
611 GtkWidget
* button
= gtk_button_new_with_mnemonic(
612 ui::ConvertAcceleratorsFromWindowsStyle(
613 l10n_util::GetStringUTF8(ids_id
)).c_str());
614 gtk_button_set_image(GTK_BUTTON(button
),
615 gtk_image_new_from_stock(stock_id
,
616 GTK_ICON_SIZE_BUTTON
));
620 GtkWidget
* CreateEntryImageHBox(GtkWidget
* entry
, GtkWidget
* image
) {
621 GtkWidget
* hbox
= gtk_hbox_new(FALSE
, ui::kControlSpacing
);
622 gtk_box_pack_start(GTK_BOX(hbox
), entry
, TRUE
, TRUE
, 0);
623 gtk_box_pack_start(GTK_BOX(hbox
), image
, FALSE
, FALSE
, 0);
627 void SetLabelColor(GtkWidget
* label
, const GdkColor
* color
) {
628 gtk_widget_modify_fg(label
, GTK_STATE_NORMAL
, color
);
629 gtk_widget_modify_fg(label
, GTK_STATE_ACTIVE
, color
);
630 gtk_widget_modify_fg(label
, GTK_STATE_PRELIGHT
, color
);
631 gtk_widget_modify_fg(label
, GTK_STATE_INSENSITIVE
, color
);
634 GtkWidget
* IndentWidget(GtkWidget
* content
) {
635 GtkWidget
* content_alignment
= gtk_alignment_new(0.0, 0.5, 1.0, 1.0);
636 gtk_alignment_set_padding(GTK_ALIGNMENT(content_alignment
), 0, 0,
637 ui::kGroupIndent
, 0);
638 gtk_container_add(GTK_CONTAINER(content_alignment
), content
);
639 return content_alignment
;
642 GdkPoint
MakeBidiGdkPoint(gint x
, gint y
, gint width
, bool ltr
) {
643 GdkPoint point
= {ltr
? x
: width
- x
, y
};
647 std::string
BuildTooltipTitleFor(base::string16 title
, const GURL
& url
) {
648 const std::string
& url_str
= url
.possibly_invalid_spec();
649 const std::string
& title_str
= base::UTF16ToUTF8(title
);
651 std::string truncated_url
= base::UTF16ToUTF8(gfx::TruncateString(
652 base::UTF8ToUTF16(url_str
), kMaxTooltipURLLength
));
653 gchar
* escaped_url_cstr
= g_markup_escape_text(truncated_url
.c_str(),
654 truncated_url
.size());
655 std::string
escaped_url(escaped_url_cstr
);
656 g_free(escaped_url_cstr
);
658 if (url_str
== title_str
|| title
.empty()) {
661 std::string truncated_title
= base::UTF16ToUTF8(gfx::TruncateString(
662 title
, kMaxTooltipTitleLength
));
663 gchar
* escaped_title_cstr
= g_markup_escape_text(truncated_title
.c_str(),
664 truncated_title
.size());
665 std::string
escaped_title(escaped_title_cstr
);
666 g_free(escaped_title_cstr
);
668 if (!escaped_url
.empty())
669 return std::string("<b>") + escaped_title
+ "</b>\n" + escaped_url
;
671 return std::string("<b>") + escaped_title
+ "</b>";
675 void DrawTextEntryBackground(GtkWidget
* offscreen_entry
,
676 GtkWidget
* widget_to_draw_on
,
677 GdkRectangle
* dirty_rec
,
679 GtkStyle
* gtk_owned_style
= gtk_rc_get_style(offscreen_entry
);
680 // GTK owns the above and we're going to have to make our own copy of it
682 GtkStyle
* our_style
= gtk_style_copy(gtk_owned_style
);
683 our_style
= gtk_style_attach(our_style
, widget_to_draw_on
->window
);
685 // TODO(erg): Draw the focus ring if appropriate...
687 // We're using GTK rendering; draw a GTK entry widget onto the background.
688 gtk_paint_shadow(our_style
, widget_to_draw_on
->window
,
689 GTK_STATE_NORMAL
, GTK_SHADOW_IN
, dirty_rec
,
690 widget_to_draw_on
, "entry",
691 rec
->x
, rec
->y
, rec
->width
, rec
->height
);
693 // Draw the interior background (not all themes draw the entry background
694 // above; this is a noop on themes that do).
695 gint xborder
= our_style
->xthickness
;
696 gint yborder
= our_style
->ythickness
;
697 gint width
= rec
->width
- 2 * xborder
;
698 gint height
= rec
->height
- 2 * yborder
;
699 if (width
> 0 && height
> 0) {
700 gtk_paint_flat_box(our_style
, widget_to_draw_on
->window
,
701 GTK_STATE_NORMAL
, GTK_SHADOW_NONE
, dirty_rec
,
702 widget_to_draw_on
, "entry_bg",
703 rec
->x
+ xborder
, rec
->y
+ yborder
,
707 gtk_style_detach(our_style
);
708 g_object_unref(our_style
);
711 void SetLayoutText(PangoLayout
* layout
, const base::string16
& text
) {
712 // Pango is really easy to overflow and send into a computational death
713 // spiral that can corrupt the screen. Assume that we'll never have more than
714 // 2000 characters, which should be a safe assumption until we all get robot
715 // eyes. http://crbug.com/66576
716 std::string text_utf8
= base::UTF16ToUTF8(text
);
717 if (text_utf8
.length() > 2000)
718 text_utf8
= text_utf8
.substr(0, 2000);
720 pango_layout_set_text(layout
, text_utf8
.data(), text_utf8
.length());
723 void DrawThemedToolbarBackground(GtkWidget
* widget
,
725 GdkEventExpose
* event
,
726 const gfx::Point
& tabstrip_origin
,
727 GtkThemeService
* theme_service
) {
728 // Fill the entire region with the toolbar color.
729 GdkColor color
= theme_service
->GetGdkColor(
730 ThemeProperties::COLOR_TOOLBAR
);
731 gdk_cairo_set_source_color(cr
, &color
);
734 // The toolbar is supposed to blend in with the active tab, so we have to pass
735 // coordinates for the IDR_THEME_TOOLBAR bitmap relative to the top of the
737 const gfx::Image background
=
738 theme_service
->GetImageNamed(IDR_THEME_TOOLBAR
);
739 background
.ToCairo()->SetSource(cr
, widget
,
740 tabstrip_origin
.x(), tabstrip_origin
.y());
741 // We tile the toolbar background in both directions.
742 cairo_pattern_set_extend(cairo_get_source(cr
), CAIRO_EXTEND_REPEAT
);
746 event
->area
.x
+ event
->area
.width
- tabstrip_origin
.x(),
747 event
->area
.y
+ event
->area
.height
- tabstrip_origin
.y());
751 void DrawFullImage(cairo_t
* cr
,
753 const gfx::Image
& image
,
756 gfx::CairoCachedSurface
* surface
= image
.ToCairo();
757 surface
->SetSource(cr
, widget
, dest_x
, dest_y
);
758 cairo_pattern_set_extend(cairo_get_source(cr
), CAIRO_EXTEND_REPEAT
);
759 cairo_rectangle(cr
, dest_x
, dest_y
, surface
->Width(), surface
->Height());
763 GdkColor
AverageColors(GdkColor color_one
, GdkColor color_two
) {
764 GdkColor average_color
;
765 average_color
.pixel
= 0;
766 average_color
.red
= (color_one
.red
+ color_two
.red
) / 2;
767 average_color
.green
= (color_one
.green
+ color_two
.green
) / 2;
768 average_color
.blue
= (color_one
.blue
+ color_two
.blue
) / 2;
769 return average_color
;
772 void SetAlwaysShowImage(GtkWidget
* image_menu_item
) {
773 gtk_image_menu_item_set_always_show_image(
774 GTK_IMAGE_MENU_ITEM(image_menu_item
), TRUE
);
777 gfx::Rect
GetWidgetRectRelativeToToplevel(GtkWidget
* widget
) {
778 DCHECK(gtk_widget_get_realized(widget
));
780 GtkWidget
* toplevel
= gtk_widget_get_toplevel(widget
);
782 DCHECK(gtk_widget_get_realized(toplevel
));
785 gtk_widget_translate_coordinates(widget
,
790 GtkAllocation allocation
;
791 gtk_widget_get_allocation(widget
, &allocation
);
792 return gfx::Rect(x
, y
, allocation
.width
, allocation
.height
);
795 void SuppressDefaultPainting(GtkWidget
* container
) {
796 g_signal_connect(container
, "expose-event",
797 G_CALLBACK(PaintNoBackground
), NULL
);
800 bool GrabAllInput(GtkWidget
* widget
) {
801 guint time
= gtk_get_current_event_time();
803 if (!gtk_widget_get_visible(widget
))
806 GdkWindow
* gdk_window
= gtk_widget_get_window(widget
);
807 if (gdk_pointer_grab(gdk_window
,
809 GdkEventMask(GDK_BUTTON_PRESS_MASK
|
810 GDK_BUTTON_RELEASE_MASK
|
811 GDK_ENTER_NOTIFY_MASK
|
812 GDK_LEAVE_NOTIFY_MASK
|
813 GDK_POINTER_MOTION_MASK
),
814 NULL
, NULL
, time
) != 0) {
818 if (gdk_keyboard_grab(gdk_window
, TRUE
, time
) != 0) {
819 gdk_display_pointer_ungrab(gdk_drawable_get_display(gdk_window
), time
);
823 gtk_grab_add(widget
);
827 gfx::Rect
WidgetBounds(GtkWidget
* widget
) {
828 // To quote the gtk docs:
830 // Widget coordinates are a bit odd; for historical reasons, they are
831 // defined as widget->window coordinates for widgets that are not
832 // GTK_NO_WINDOW widgets, and are relative to allocation.x, allocation.y
833 // for widgets that are GTK_NO_WINDOW widgets.
835 // So the base is always (0,0).
836 GtkAllocation allocation
;
837 gtk_widget_get_allocation(widget
, &allocation
);
838 return gfx::Rect(0, 0, allocation
.width
, allocation
.height
);
841 void SetWMLastUserActionTime(GtkWindow
* window
) {
842 gdk_x11_window_set_user_time(gtk_widget_get_window(GTK_WIDGET(window
)),
848 clock_gettime(CLOCK_MONOTONIC
, &ts
);
849 return ts
.tv_sec
* 1000 + ts
.tv_nsec
/ 1000000;
852 bool URLFromPrimarySelection(Profile
* profile
, GURL
* url
) {
853 GtkClipboard
* clipboard
= gtk_clipboard_get(GDK_SELECTION_PRIMARY
);
855 gchar
* selection_text
= gtk_clipboard_wait_for_text(clipboard
);
859 // Use autocomplete to clean up the text, going so far as to turn it into
860 // a search query if necessary.
861 AutocompleteMatch match
;
862 AutocompleteClassifierFactory::GetForProfile(profile
)->Classify(
863 base::UTF8ToUTF16(selection_text
), false, false, &match
, NULL
);
864 g_free(selection_text
);
865 if (!match
.destination_url
.is_valid())
868 *url
= match
.destination_url
;
872 bool AddWindowAlphaChannel(GtkWidget
* window
) {
873 GdkScreen
* screen
= gtk_widget_get_screen(window
);
874 GdkColormap
* rgba
= gdk_screen_get_rgba_colormap(screen
);
876 gtk_widget_set_colormap(window
, rgba
);
881 void GetTextColors(GdkColor
* normal_base
,
882 GdkColor
* selected_base
,
883 GdkColor
* normal_text
,
884 GdkColor
* selected_text
) {
885 GtkWidget
* fake_entry
= gtk_entry_new();
886 GtkStyle
* style
= gtk_rc_get_style(fake_entry
);
889 *normal_base
= style
->base
[GTK_STATE_NORMAL
];
891 *selected_base
= style
->base
[GTK_STATE_SELECTED
];
893 *normal_text
= style
->text
[GTK_STATE_NORMAL
];
895 *selected_text
= style
->text
[GTK_STATE_SELECTED
];
897 g_object_ref_sink(fake_entry
);
898 g_object_unref(fake_entry
);
901 void ShowDialog(GtkWidget
* dialog
) {
902 gtk_widget_show_all(dialog
);
905 void ShowDialogWithLocalizedSize(GtkWidget
* dialog
,
909 gtk_widget_realize(dialog
);
910 SetWindowSizeFromResources(GTK_WINDOW(dialog
),
914 gtk_widget_show_all(dialog
);
917 void ShowDialogWithMinLocalizedWidth(GtkWidget
* dialog
,
919 gtk_widget_show_all(dialog
);
921 // Suggest a minimum size.
924 gtk_widget_size_request(dialog
, &req
);
925 gtk_util::GetWidgetSizeFromResources(dialog
, width_id
, 0, &width
, NULL
);
926 if (width
> req
.width
)
927 gtk_widget_set_size_request(dialog
, width
, -1);
930 void PresentWindow(GtkWidget
* window
, int timestamp
) {
932 gtk_window_present_with_time(GTK_WINDOW(window
), timestamp
);
934 gtk_window_present(GTK_WINDOW(window
));
937 gfx::Rect
GetDialogBounds(GtkWidget
* dialog
) {
938 gint x
= 0, y
= 0, width
= 1, height
= 1;
939 gtk_window_get_position(GTK_WINDOW(dialog
), &x
, &y
);
940 gtk_window_get_size(GTK_WINDOW(dialog
), &width
, &height
);
942 return gfx::Rect(x
, y
, width
, height
);
945 base::string16
GetStockPreferencesMenuLabel() {
946 GtkStockItem stock_item
;
947 base::string16 preferences
;
948 if (gtk_stock_lookup(GTK_STOCK_PREFERENCES
, &stock_item
)) {
949 const base::char16 kUnderscore
[] = { '_', 0 };
950 base::RemoveChars(base::UTF8ToUTF16(stock_item
.label
),
951 kUnderscore
, &preferences
);
956 bool IsWidgetAncestryVisible(GtkWidget
* widget
) {
957 GtkWidget
* parent
= widget
;
958 while (parent
&& gtk_widget_get_visible(parent
))
959 parent
= gtk_widget_get_parent(parent
);
963 void SetLabelWidth(GtkWidget
* label
, int pixel_width
) {
964 gtk_label_set_line_wrap(GTK_LABEL(label
), TRUE
);
965 gtk_misc_set_alignment(GTK_MISC(label
), 0, 0.5);
967 // Do the simple thing in LTR because the bug only affects right-aligned
968 // text. Also, when using the workaround, the label tries to maintain
969 // uniform line-length, which we don't really want.
970 if (gtk_widget_get_direction(label
) == GTK_TEXT_DIR_LTR
) {
971 gtk_widget_set_size_request(label
, pixel_width
, -1);
973 // The label has to be realized before we can adjust its width.
974 if (gtk_widget_get_realized(label
)) {
975 OnLabelRealize(label
, GINT_TO_POINTER(pixel_width
));
977 g_signal_connect(label
, "realize", G_CALLBACK(OnLabelRealize
),
978 GINT_TO_POINTER(pixel_width
));
983 void InitLabelSizeRequestAndEllipsizeMode(GtkWidget
* label
) {
985 gtk_label_set_ellipsize(GTK_LABEL(label
), PANGO_ELLIPSIZE_NONE
);
986 gtk_widget_set_size_request(label
, -1, -1);
987 gtk_widget_size_request(label
, &size
);
988 gtk_widget_set_size_request(label
, size
.width
, size
.height
);
989 gtk_label_set_ellipsize(GTK_LABEL(label
), PANGO_ELLIPSIZE_END
);
992 void ApplyMessageDialogQuirks(GtkWidget
* dialog
) {
993 if (gtk_window_get_modal(GTK_WINDOW(dialog
))) {
994 // Work around a KDE 3 window manager bug.
995 scoped_ptr
<base::Environment
> env(base::Environment::Create());
996 if (base::nix::DESKTOP_ENVIRONMENT_KDE3
==
997 base::nix::GetDesktopEnvironment(env
.get()))
998 gtk_window_set_skip_taskbar_hint(GTK_WINDOW(dialog
), FALSE
);
1002 } // namespace gtk_util