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_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"
51 // These conflict with base/tracked_objects.h, so need to come last.
52 #include <gdk/gdkx.h> // NOLINT
56 #if defined(GOOGLE_CHROME_BUILD)
57 static const char* kIconName
= "google-chrome";
59 static const char* kIconName
= "chromium-browser";
62 const char kBoldLabelMarkup
[] = "<span weight='bold'>%s</span>";
64 // Max size of each component of the button tooltips.
65 const size_t kMaxTooltipTitleLength
= 100;
66 const size_t kMaxTooltipURLLength
= 400;
68 // Callback used in RemoveAllChildren.
69 void RemoveWidget(GtkWidget
* widget
, gpointer container
) {
70 gtk_container_remove(GTK_CONTAINER(container
), widget
);
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
,
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
);
83 gint button_mask
= GPOINTER_TO_INT(userdata
);
84 if (button_mask
& (1 << event
->button
))
85 gtk_button_pressed(GTK_BUTTON(widget
));
91 gboolean
OnMouseButtonReleased(GtkWidget
* widget
, GdkEventButton
* event
,
93 gint button_mask
= GPOINTER_TO_INT(userdata
);
94 if (button_mask
&& (1 << event
->button
))
95 gtk_button_released(GTK_BUTTON(widget
));
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";
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
));
111 // This technique (max of char and digit widths) matches the code in
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
));
117 pango_font_metrics_unref(metrics
);
118 g_object_unref(context
);
123 void OnLabelRealize(GtkWidget
* label
, gpointer pixel_width
) {
124 gtk_label_set_width_chars(
126 GetCharacterWidthForPixels(label
, GPOINTER_TO_INT(pixel_width
)));
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());
140 // Returns the avatar icon for |profile|.
142 // Returns NULL if there is only one profile; always returns an icon for
143 // Incognito profiles.
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();
153 const ProfileInfoCache
& cache
=
154 g_browser_process
->profile_manager()->GetProfileInfoCache();
156 if (!profiles::IsMultipleProfilesEnabled() ||
157 cache
.GetNumberOfProfiles() < 2)
160 const size_t index
= cache
.GetIndexOfProfileWithPath(profile
->GetPath());
162 return (index
!= std::string::npos
?
163 cache
.GetAvatarIconOfProfileAtIndex(index
).ToGdkPixbuf() :
164 static_cast<GdkPixbuf
*>(NULL
));
167 // Gets the Chrome product icon.
169 // If it doesn't find the icon in |theme|, it looks among the icons packaged
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).
178 // The caller is responsible for calling g_object_unref() on the returned
180 GdkPixbuf
* GetChromeIcon(GtkIconTheme
* theme
, const int size
) {
181 if (gtk_icon_theme_has_icon(theme
, kIconName
)) {
183 gtk_icon_theme_load_icon(theme
,
186 static_cast<GtkIconLookupFlags
>(0),
188 GdkPixbuf
* icon_copy
= gdk_pixbuf_copy(icon
);
189 g_object_unref(icon
);
193 ui::ResourceBundle
& rb
= ui::ResourceBundle::GetSharedInstance();
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;
203 return gdk_pixbuf_copy(rb
.GetNativeImageNamed(id
).ToGdkPixbuf());
206 // Adds |emblem| to the bottom-right corner of |icon|.
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
);
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.
225 gdk_pixbuf_composite(emblem
, icon
,
229 emblem_scale
, emblem_scale
,
230 GDK_INTERP_BILINEAR
, 255);
233 // Returns a list containing Chrome icons of various sizes emblemed with the
234 // |profile|'s avatar.
236 // If there is only one profile, no emblem is added, but icons for Incognito
237 // profiles will always get the Incognito emblem.
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
)));
246 GdkPixbuf
* icon_16
= GetChromeIcon(theme
, 16);
247 GdkPixbuf
* icon_32
= GetChromeIcon(theme
, 32);
248 GdkPixbuf
* icon_64
= GetChromeIcon(theme
, 64);
250 const GdkPixbuf
* avatar
= GetAvatarIcon(profile
);
252 AddEmblem(avatar
, icon_16
);
253 AddEmblem(avatar
, icon_32
);
254 AddEmblem(avatar
, icon_64
);
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
);
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
,
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
),
276 g_list_free(children
);
285 GtkWidget
* CreateLabeledControlsGroup(std::vector
<GtkWidget
*>* labels
,
286 const char* 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
);
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);
299 labels
->push_back(label
);
301 gtk_table_attach(GTK_TABLE(table
), label
,
305 gtk_table_attach_defaults(GTK_TABLE(table
), control
,
307 text
= va_arg(ap
, const char*);
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();
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
);
329 GtkWidget
* LeftAlignMisc(GtkWidget
* misc
) {
330 gtk_misc_set_alignment(GTK_MISC(misc
), 0, 0.5);
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
);
340 return LeftAlignMisc(label
);
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
));
353 *width
= static_cast<int>(
354 pango_font_metrics_get_approximate_char_width(metrics
) *
355 width_chars
/ PANGO_SCALE
);
358 *height
= static_cast<int>(
359 (pango_font_metrics_get_ascent(metrics
) +
360 pango_font_metrics_get_descent(metrics
)) *
361 height_lines
/ PANGO_SCALE
);
363 pango_font_metrics_unref(metrics
);
364 g_object_unref(context
);
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";
375 base::StringToDouble(l10n_util::GetStringUTF8(width_chars
), &chars
);
379 base::StringToDouble(l10n_util::GetStringUTF8(height_lines
), &lines
);
381 GetWidgetSizeFromCharacters(widget
, chars
, lines
, width
, height
);
384 void SetWindowSizeFromResources(GtkWindow
* window
,
385 int width_id
, int height_id
, bool resizable
) {
388 gtk_util::GetWidgetSizeFromResources(GTK_WIDGET(window
), width_id
, height_id
,
389 (width_id
!= -1) ? &width
: NULL
,
390 (height_id
!= -1) ? &height
: NULL
);
393 gtk_window_set_default_size(window
, width
, height
);
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.
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.
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(
409 width
== -1 ? -1 : std::max(width
, requisition
.width
),
410 height
== -1 ? -1 : std::max(height
, requisition
.height
));
412 gtk_window_set_resizable(window
, resizable
? TRUE
: FALSE
);
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());
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
));
429 g_list_free(all_windows
);
431 g_object_unref(window_group
);
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
;
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
);
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
);
450 transient_windows
.push_back(window
);
452 GtkWindowGroup
* window_group
= gtk_window_group_new();
453 gtk_window_group_add_window(window_group
, window
);
454 g_object_unref(window_group
);
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
);
465 g_list_free(windows
);
469 void RemoveAllChildren(GtkWidget
* container
) {
470 gtk_container_foreach(GTK_CONTAINER(container
), RemoveWidget
, container
);
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());
482 void UndoForceFontSize(GtkWidget
* widget
) {
483 gtk_widget_modify_font(widget
, NULL
);
486 gfx::Size
GetWidgetSize(GtkWidget
* widget
) {
488 gtk_widget_size_request(widget
, &size
);
489 return gfx::Size(size
.width
, size
.height
);
492 void ConvertWidgetPointToScreen(GtkWidget
* widget
, gfx::Point
* p
) {
496 *p
+= ui::GetWidgetScreenOffset(widget
);
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);
504 gtk_box_pack_end(GTK_BOX(hbox
), centering_vbox
, FALSE
, FALSE
, padding
);
506 gtk_box_pack_start(GTK_BOX(hbox
), centering_vbox
, FALSE
, FALSE
, padding
);
508 return centering_vbox
;
511 void SetButtonClickableByMouseButtons(GtkWidget
* button
,
512 bool left
, bool middle
, bool right
) {
513 gint button_mask
= 0;
515 button_mask
|= 1 << 1;
517 button_mask
|= 1 << 2;
519 button_mask
|= 1 << 3;
520 void* userdata
= GINT_TO_POINTER(button_mask
);
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
);
528 void SetButtonTriggersNavigation(GtkWidget
* button
) {
529 SetButtonClickableByMouseButtons(button
, true, true, false);
532 int MirroredLeftPointForRect(GtkWidget
* widget
, const gfx::Rect
& bounds
) {
533 if (!base::i18n::IsRTL())
536 GtkAllocation allocation
;
537 gtk_widget_get_allocation(widget
, &allocation
);
538 return allocation
.width
- bounds
.x() - bounds
.width();
541 int MirroredRightPointForRect(GtkWidget
* widget
, const gfx::Rect
& bounds
) {
542 if (!base::i18n::IsRTL())
543 return bounds
.right();
545 GtkAllocation allocation
;
546 gtk_widget_get_allocation(widget
, &allocation
);
547 return allocation
.width
- bounds
.x();
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
;
559 bool WidgetContainsCursor(GtkWidget
* widget
) {
562 gtk_widget_get_pointer(widget
, &x
, &y
);
563 return WidgetBounds(widget
).Contains(x
, y
);
566 void SetDefaultWindowIcon(GtkWindow
* window
) {
567 GtkIconTheme
* theme
=
568 gtk_icon_theme_get_for_screen(gtk_widget_get_screen(GTK_WIDGET(window
)));
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
);
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
);
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
);
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
);
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
,
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
));
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);
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
);
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
;
643 GdkPoint
MakeBidiGdkPoint(gint x
, gint y
, gint width
, bool ltr
) {
644 GdkPoint point
= {ltr
? x
: width
- x
, y
};
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
);
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
);
659 if (url_str
== title_str
|| title
.empty()) {
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
);
669 if (!escaped_url
.empty())
670 return std::string("<b>") + escaped_title
+ "</b>\n" + escaped_url
;
672 return std::string("<b>") + escaped_title
+ "</b>";
676 void DrawTextEntryBackground(GtkWidget
* offscreen_entry
,
677 GtkWidget
* widget_to_draw_on
,
678 GdkRectangle
* dirty_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
683 GtkStyle
* our_style
= gtk_style_copy(gtk_owned_style
);
684 our_style
= gtk_style_attach(our_style
, widget_to_draw_on
->window
);
686 // TODO(erg): Draw the focus ring if appropriate...
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
);
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
,
708 gtk_style_detach(our_style
);
709 g_object_unref(our_style
);
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);
721 pango_layout_set_text(layout
, text_utf8
.data(), text_utf8
.length());
724 void DrawThemedToolbarBackground(GtkWidget
* widget
,
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
);
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
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
);
747 event
->area
.x
+ event
->area
.width
- tabstrip_origin
.x(),
748 event
->area
.y
+ event
->area
.height
- tabstrip_origin
.y());
752 void DrawFullImage(cairo_t
* cr
,
754 const gfx::Image
& image
,
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());
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
;
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
);
778 gfx::Rect
GetWidgetRectRelativeToToplevel(GtkWidget
* widget
) {
779 DCHECK(gtk_widget_get_realized(widget
));
781 GtkWidget
* toplevel
= gtk_widget_get_toplevel(widget
);
783 DCHECK(gtk_widget_get_realized(toplevel
));
786 gtk_widget_translate_coordinates(widget
,
791 GtkAllocation allocation
;
792 gtk_widget_get_allocation(widget
, &allocation
);
793 return gfx::Rect(x
, y
, allocation
.width
, allocation
.height
);
796 void SuppressDefaultPainting(GtkWidget
* container
) {
797 g_signal_connect(container
, "expose-event",
798 G_CALLBACK(PaintNoBackground
), NULL
);
801 bool GrabAllInput(GtkWidget
* widget
) {
802 guint time
= gtk_get_current_event_time();
804 if (!gtk_widget_get_visible(widget
))
807 GdkWindow
* gdk_window
= gtk_widget_get_window(widget
);
808 if (gdk_pointer_grab(gdk_window
,
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) {
819 if (gdk_keyboard_grab(gdk_window
, TRUE
, time
) != 0) {
820 gdk_display_pointer_ungrab(gdk_drawable_get_display(gdk_window
), time
);
824 gtk_grab_add(widget
);
828 gfx::Rect
WidgetBounds(GtkWidget
* widget
) {
829 // To quote the gtk docs:
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.
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
);
842 void SetWMLastUserActionTime(GtkWindow
* window
) {
843 gdk_x11_window_set_user_time(gtk_widget_get_window(GTK_WIDGET(window
)),
849 clock_gettime(CLOCK_MONOTONIC
, &ts
);
850 return ts
.tv_sec
* 1000 + ts
.tv_nsec
/ 1000000;
853 bool URLFromPrimarySelection(Profile
* profile
, GURL
* url
) {
854 GtkClipboard
* clipboard
= gtk_clipboard_get(GDK_SELECTION_PRIMARY
);
856 gchar
* selection_text
= gtk_clipboard_wait_for_text(clipboard
);
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())
870 *url
= match
.destination_url
;
874 bool AddWindowAlphaChannel(GtkWidget
* window
) {
875 GdkScreen
* screen
= gtk_widget_get_screen(window
);
876 GdkColormap
* rgba
= gdk_screen_get_rgba_colormap(screen
);
878 gtk_widget_set_colormap(window
, rgba
);
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
);
891 *normal_base
= style
->base
[GTK_STATE_NORMAL
];
893 *selected_base
= style
->base
[GTK_STATE_SELECTED
];
895 *normal_text
= style
->text
[GTK_STATE_NORMAL
];
897 *selected_text
= style
->text
[GTK_STATE_SELECTED
];
899 g_object_ref_sink(fake_entry
);
900 g_object_unref(fake_entry
);
903 void ShowDialog(GtkWidget
* dialog
) {
904 gtk_widget_show_all(dialog
);
907 void ShowDialogWithLocalizedSize(GtkWidget
* dialog
,
911 gtk_widget_realize(dialog
);
912 SetWindowSizeFromResources(GTK_WINDOW(dialog
),
916 gtk_widget_show_all(dialog
);
919 void ShowDialogWithMinLocalizedWidth(GtkWidget
* dialog
,
921 gtk_widget_show_all(dialog
);
923 // Suggest a minimum size.
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);
932 void PresentWindow(GtkWidget
* window
, int timestamp
) {
934 gtk_window_present_with_time(GTK_WINDOW(window
), timestamp
);
936 gtk_window_present(GTK_WINDOW(window
));
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
);
944 return gfx::Rect(x
, y
, width
, height
);
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
);
958 bool IsWidgetAncestryVisible(GtkWidget
* widget
) {
959 GtkWidget
* parent
= widget
;
960 while (parent
&& gtk_widget_get_visible(parent
))
961 parent
= gtk_widget_get_parent(parent
);
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);
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);
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
));
979 g_signal_connect(label
, "realize", G_CALLBACK(OnLabelRealize
),
980 GINT_TO_POINTER(pixel_width
));
985 void InitLabelSizeRequestAndEllipsizeMode(GtkWidget
* label
) {
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
);
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
);
1004 } // namespace gtk_util