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_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"
48#include "url/gurl.h"
49
50// These conflict with base/tracked_objects.h, so need to come last.
51#include <gdk/gdkx.h>  // NOLINT
52
53namespace {
54
55#if defined(GOOGLE_CHROME_BUILD)
56static const char* kIconName = "google-chrome";
57#else
58static const char* kIconName = "chromium-browser";
59#endif
60
61const char kBoldLabelMarkup[] = "<span weight='bold'>%s</span>";
62
63// Max size of each component of the button tooltips.
64const size_t kMaxTooltipTitleLength = 100;
65const size_t kMaxTooltipURLLength = 400;
66
67// Callback used in RemoveAllChildren.
68void RemoveWidget(GtkWidget* widget, gpointer container) {
69  gtk_container_remove(GTK_CONTAINER(container), widget);
70}
71
72// These two functions are copped almost directly from gtk core. The only
73// difference is that they accept middle clicks.
74gboolean OnMouseButtonPressed(GtkWidget* widget, GdkEventButton* event,
75                              gpointer userdata) {
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);
80    }
81
82    gint button_mask = GPOINTER_TO_INT(userdata);
83    if (button_mask & (1 << event->button))
84      gtk_button_pressed(GTK_BUTTON(widget));
85  }
86
87  return TRUE;
88}
89
90gboolean OnMouseButtonReleased(GtkWidget* widget, GdkEventButton* event,
91                               gpointer userdata) {
92  gint button_mask = GPOINTER_TO_INT(userdata);
93  if (button_mask && (1 << event->button))
94    gtk_button_released(GTK_BUTTON(widget));
95
96  return TRUE;
97}
98
99// Returns the approximate number of characters that can horizontally fit in
100// |pixel_width| pixels.
101int GetCharacterWidthForPixels(GtkWidget* widget, int pixel_width) {
102  DCHECK(gtk_widget_get_realized(widget))
103      << " widget must be realized to compute font metrics correctly";
104
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));
109
110  // This technique (max of char and digit widths) matches the code in
111  // gtklabel.c.
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));
115
116  pango_font_metrics_unref(metrics);
117  g_object_unref(context);
118
119  return char_width;
120}
121
122void OnLabelRealize(GtkWidget* label, gpointer pixel_width) {
123  gtk_label_set_width_chars(
124      GTK_LABEL(label),
125      GetCharacterWidthForPixels(label, GPOINTER_TO_INT(pixel_width)));
126}
127
128// Ownership of |icon_list| is passed to the caller.
129GList* 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());
136  return icon_list;
137}
138
139// Returns the avatar icon for |profile|.
140//
141// Returns NULL if there is only one profile; always returns an icon for
142// Incognito profiles.
143//
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.
146GdkPixbuf* GetAvatarIcon(Profile* profile) {
147  if (profile->IsOffTheRecord()) {
148    ui::ResourceBundle& rb = ui::ResourceBundle::GetSharedInstance();
149    return rb.GetNativeImageNamed(IDR_OTR_ICON).ToGdkPixbuf();
150  }
151
152  const ProfileInfoCache& cache =
153      g_browser_process->profile_manager()->GetProfileInfoCache();
154
155  if (!profiles::IsMultipleProfilesEnabled() ||
156      cache.GetNumberOfProfiles() < 2)
157    return NULL;
158
159  const size_t index = cache.GetIndexOfProfileWithPath(profile->GetPath());
160
161  return (index != std::string::npos ?
162          cache.GetAvatarIconOfProfileAtIndex(index).ToGdkPixbuf() :
163          static_cast<GdkPixbuf*>(NULL));
164}
165
166// Gets the Chrome product icon.
167//
168// If it doesn't find the icon in |theme|, it looks among the icons packaged
169// with Chrome.
170//
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).
176//
177// The caller is responsible for calling g_object_unref() on the returned
178// pixbuf.
179GdkPixbuf* GetChromeIcon(GtkIconTheme* theme, const int size) {
180  if (gtk_icon_theme_has_icon(theme, kIconName)) {
181    GdkPixbuf* icon =
182        gtk_icon_theme_load_icon(theme,
183                                 kIconName,
184                                 size,
185                                 static_cast<GtkIconLookupFlags>(0),
186                                 0);
187    GdkPixbuf* icon_copy = gdk_pixbuf_copy(icon);
188    g_object_unref(icon);
189    return icon_copy;
190  }
191
192  ui::ResourceBundle& rb = ui::ResourceBundle::GetSharedInstance();
193  int id = 0;
194
195  switch (size) {
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;
200  }
201
202  return gdk_pixbuf_copy(rb.GetNativeImageNamed(id).ToGdkPixbuf());
203}
204
205// Adds |emblem| to the bottom-right corner of |icon|.
206//
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.
211void 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);
216
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.
223
224  gdk_pixbuf_composite(emblem, icon,
225                       x, y,
226                       dest_w, dest_h,
227                       x, y,
228                       emblem_scale, emblem_scale,
229                       GDK_INTERP_BILINEAR, 255);
230}
231
232// Returns a list containing Chrome icons of various sizes emblemed with the
233// |profile|'s avatar.
234//
235// If there is only one profile, no emblem is added, but icons for Incognito
236// profiles will always get the Incognito emblem.
237//
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.
241GList* GetIconListWithAvatars(GtkWindow* window, Profile* profile) {
242  GtkIconTheme* theme =
243      gtk_icon_theme_get_for_screen(gtk_widget_get_screen(GTK_WIDGET(window)));
244
245  GdkPixbuf* icon_16 = GetChromeIcon(theme, 16);
246  GdkPixbuf* icon_32 = GetChromeIcon(theme, 32);
247  GdkPixbuf* icon_64 = GetChromeIcon(theme, 64);
248
249  const GdkPixbuf* avatar = GetAvatarIcon(profile);
250  if (avatar) {
251    AddEmblem(avatar, icon_16);
252    AddEmblem(avatar, icon_32);
253    AddEmblem(avatar, icon_64);
254  }
255
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);
260
261  return icon_list;
262}
263
264// Expose event handler for a container that simply suppresses the default
265// drawing and propagates the expose event to the container's children.
266gboolean PaintNoBackground(GtkWidget* widget,
267                           GdkEventExpose* event,
268                           gpointer unused) {
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),
273                                   event);
274  }
275  g_list_free(children);
276
277  return TRUE;
278}
279
280}  // namespace
281
282namespace gtk_util {
283
284GtkWidget* CreateLabeledControlsGroup(std::vector<GtkWidget*>* labels,
285                                      const char* text, ...) {
286  va_list ap;
287  va_start(ap, 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);
291
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);
297    if (labels)
298      labels->push_back(label);
299
300    gtk_table_attach(GTK_TABLE(table), label,
301                 0, 1, row, row + 1,
302                 GTK_FILL, GTK_FILL,
303                 0, 0);
304    gtk_table_attach_defaults(GTK_TABLE(table), control,
305                              1, 2, row, row + 1);
306    text = va_arg(ap, const char*);
307  }
308  va_end(ap);
309
310  return table;
311}
312
313GtkWidget* 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();
319  if (color)
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);
325  return ebox;
326}
327
328GtkWidget* LeftAlignMisc(GtkWidget* misc) {
329  gtk_misc_set_alignment(GTK_MISC(misc), 0, 0.5);
330  return misc;
331}
332
333GtkWidget* 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);
337  g_free(markup);
338
339  return LeftAlignMisc(label);
340}
341
342void 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));
351  if (width) {
352    *width = static_cast<int>(
353        pango_font_metrics_get_approximate_char_width(metrics) *
354        width_chars / PANGO_SCALE);
355  }
356  if (height) {
357    *height = static_cast<int>(
358        (pango_font_metrics_get_ascent(metrics) +
359        pango_font_metrics_get_descent(metrics)) *
360        height_lines / PANGO_SCALE);
361  }
362  pango_font_metrics_unref(metrics);
363  g_object_unref(context);
364}
365
366void 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";
371
372  double chars = 0;
373  if (width)
374    base::StringToDouble(l10n_util::GetStringUTF8(width_chars), &chars);
375
376  double lines = 0;
377  if (height)
378    base::StringToDouble(l10n_util::GetStringUTF8(height_lines), &lines);
379
380  GetWidgetSizeFromCharacters(widget, chars, lines, width, height);
381}
382
383void SetWindowSizeFromResources(GtkWindow* window,
384                                int width_id, int height_id, bool resizable) {
385  int width = -1;
386  int height = -1;
387  gtk_util::GetWidgetSizeFromResources(GTK_WIDGET(window), width_id, height_id,
388                                       (width_id != -1) ? &width : NULL,
389                                       (height_id != -1) ? &height : NULL);
390
391  if (resizable) {
392    gtk_window_set_default_size(window, width, height);
393  } else {
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.
398    //
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.
402
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(
407        GTK_WIDGET(window),
408        width == -1 ? -1 : std::max(width, requisition.width),
409        height == -1 ? -1 : std::max(height, requisition.height));
410  }
411  gtk_window_set_resizable(window, resizable ? TRUE : FALSE);
412}
413
414void 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());
423
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));
427    }
428    g_list_free(all_windows);
429  }
430  g_object_unref(window_group);
431}
432
433void 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;
439
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);
444
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);
448      if (transient_for) {
449        transient_windows.push_back(window);
450      } else {
451        GtkWindowGroup* window_group = gtk_window_group_new();
452        gtk_window_group_add_window(window_group, window);
453        g_object_unref(window_group);
454      }
455    }
456
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);
463    }
464    g_list_free(windows);
465  }
466}
467
468void RemoveAllChildren(GtkWidget* container) {
469  gtk_container_foreach(GTK_CONTAINER(container), RemoveWidget, container);
470}
471
472void 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());
479}
480
481void UndoForceFontSize(GtkWidget* widget) {
482  gtk_widget_modify_font(widget, NULL);
483}
484
485gfx::Size GetWidgetSize(GtkWidget* widget) {
486  GtkRequisition size;
487  gtk_widget_size_request(widget, &size);
488  return gfx::Size(size.width, size.height);
489}
490
491void ConvertWidgetPointToScreen(GtkWidget* widget, gfx::Point* p) {
492  DCHECK(widget);
493  DCHECK(p);
494
495  *p += ui::GetWidgetScreenOffset(widget);
496}
497
498GtkWidget* 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);
502  if (pack_at_end)
503    gtk_box_pack_end(GTK_BOX(hbox), centering_vbox, FALSE, FALSE, padding);
504  else
505    gtk_box_pack_start(GTK_BOX(hbox), centering_vbox, FALSE, FALSE, padding);
506
507  return centering_vbox;
508}
509
510void SetButtonClickableByMouseButtons(GtkWidget* button,
511                                      bool left, bool middle, bool right) {
512  gint button_mask = 0;
513  if (left)
514    button_mask |= 1 << 1;
515  if (middle)
516    button_mask |= 1 << 2;
517  if (right)
518    button_mask |= 1 << 3;
519  void* userdata = GINT_TO_POINTER(button_mask);
520
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);
525}
526
527void SetButtonTriggersNavigation(GtkWidget* button) {
528  SetButtonClickableByMouseButtons(button, true, true, false);
529}
530
531int MirroredLeftPointForRect(GtkWidget* widget, const gfx::Rect& bounds) {
532  if (!base::i18n::IsRTL())
533    return bounds.x();
534
535  GtkAllocation allocation;
536  gtk_widget_get_allocation(widget, &allocation);
537  return allocation.width - bounds.x() - bounds.width();
538}
539
540int MirroredRightPointForRect(GtkWidget* widget, const gfx::Rect& bounds) {
541  if (!base::i18n::IsRTL())
542    return bounds.right();
543
544  GtkAllocation allocation;
545  gtk_widget_get_allocation(widget, &allocation);
546  return allocation.width - bounds.x();
547}
548
549int 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;
554  }
555  return x;
556}
557
558bool WidgetContainsCursor(GtkWidget* widget) {
559  gint x = 0;
560  gint y = 0;
561  gtk_widget_get_pointer(widget, &x, &y);
562  return WidgetBounds(widget).Contains(x, y);
563}
564
565void SetDefaultWindowIcon(GtkWindow* window) {
566  GtkIconTheme* theme =
567      gtk_icon_theme_get_for_screen(gtk_widget_get_screen(GTK_WIDGET(window)));
568
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);
576  } else {
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);
582  }
583}
584
585void 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);
590}
591
592void 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);
596}
597
598GtkWidget* 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,
605                               response_id);
606  return button;
607}
608
609GtkWidget* 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));
617  return button;
618}
619
620GtkWidget* 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);
624  return hbox;
625}
626
627void 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);
632}
633
634GtkWidget* 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;
640}
641
642GdkPoint MakeBidiGdkPoint(gint x, gint y, gint width, bool ltr) {
643  GdkPoint point = {ltr ? x : width - x, y};
644  return point;
645}
646
647std::string BuildTooltipTitleFor(base::string16 title, const GURL& url) {
648  const std::string& url_str = url.possibly_invalid_spec();
649  const std::string& title_str = UTF16ToUTF8(title);
650
651  std::string truncated_url = UTF16ToUTF8(gfx::TruncateString(
652      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);
657
658  if (url_str == title_str || title.empty()) {
659    return escaped_url;
660  } else {
661    std::string truncated_title = 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);
667
668    if (!escaped_url.empty())
669      return std::string("<b>") + escaped_title + "</b>\n" + escaped_url;
670    else
671      return std::string("<b>") + escaped_title + "</b>";
672  }
673}
674
675void DrawTextEntryBackground(GtkWidget* offscreen_entry,
676                             GtkWidget* widget_to_draw_on,
677                             GdkRectangle* dirty_rec,
678                             GdkRectangle* 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
681  // that we can edit.
682  GtkStyle* our_style = gtk_style_copy(gtk_owned_style);
683  our_style = gtk_style_attach(our_style, widget_to_draw_on->window);
684
685  // TODO(erg): Draw the focus ring if appropriate...
686
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);
692
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,
704                       width, height);
705  }
706
707  gtk_style_detach(our_style);
708  g_object_unref(our_style);
709}
710
711void 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 = UTF16ToUTF8(text);
717  if (text_utf8.length() > 2000)
718    text_utf8 = text_utf8.substr(0, 2000);
719
720  pango_layout_set_text(layout, text_utf8.data(), text_utf8.length());
721}
722
723void DrawThemedToolbarBackground(GtkWidget* widget,
724                                 cairo_t* cr,
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);
732  cairo_fill(cr);
733
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
736  // tab strip.
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);
743  cairo_rectangle(cr,
744                  tabstrip_origin.x(),
745                  tabstrip_origin.y(),
746                  event->area.x + event->area.width - tabstrip_origin.x(),
747                  event->area.y + event->area.height - tabstrip_origin.y());
748  cairo_fill(cr);
749}
750
751void DrawFullImage(cairo_t* cr,
752                   GtkWidget* widget,
753                   const gfx::Image& image,
754                   gint dest_x,
755                   gint dest_y) {
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());
760  cairo_fill(cr);
761}
762
763GdkColor 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;
770}
771
772void SetAlwaysShowImage(GtkWidget* image_menu_item) {
773  gtk_image_menu_item_set_always_show_image(
774      GTK_IMAGE_MENU_ITEM(image_menu_item), TRUE);
775}
776
777gfx::Rect GetWidgetRectRelativeToToplevel(GtkWidget* widget) {
778  DCHECK(gtk_widget_get_realized(widget));
779
780  GtkWidget* toplevel = gtk_widget_get_toplevel(widget);
781  DCHECK(toplevel);
782  DCHECK(gtk_widget_get_realized(toplevel));
783
784  gint x = 0, y = 0;
785  gtk_widget_translate_coordinates(widget,
786                                   toplevel,
787                                   0, 0,
788                                   &x, &y);
789
790  GtkAllocation allocation;
791  gtk_widget_get_allocation(widget, &allocation);
792  return gfx::Rect(x, y, allocation.width, allocation.height);
793}
794
795void SuppressDefaultPainting(GtkWidget* container) {
796  g_signal_connect(container, "expose-event",
797                   G_CALLBACK(PaintNoBackground), NULL);
798}
799
800bool GrabAllInput(GtkWidget* widget) {
801  guint time = gtk_get_current_event_time();
802
803  if (!gtk_widget_get_visible(widget))
804    return false;
805
806  GdkWindow* gdk_window = gtk_widget_get_window(widget);
807  if (gdk_pointer_grab(gdk_window,
808                       TRUE,
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) {
815    return false;
816  }
817
818  if (gdk_keyboard_grab(gdk_window, TRUE, time) != 0) {
819    gdk_display_pointer_ungrab(gdk_drawable_get_display(gdk_window), time);
820    return false;
821  }
822
823  gtk_grab_add(widget);
824  return true;
825}
826
827gfx::Rect WidgetBounds(GtkWidget* widget) {
828  // To quote the gtk docs:
829  //
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.
834  //
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);
839}
840
841void SetWMLastUserActionTime(GtkWindow* window) {
842  gdk_x11_window_set_user_time(gtk_widget_get_window(GTK_WIDGET(window)),
843                               XTimeNow());
844}
845
846guint32 XTimeNow() {
847  struct timespec ts;
848  clock_gettime(CLOCK_MONOTONIC, &ts);
849  return ts.tv_sec * 1000 + ts.tv_nsec / 1000000;
850}
851
852bool URLFromPrimarySelection(Profile* profile, GURL* url) {
853  GtkClipboard* clipboard = gtk_clipboard_get(GDK_SELECTION_PRIMARY);
854  DCHECK(clipboard);
855  gchar* selection_text = gtk_clipboard_wait_for_text(clipboard);
856  if (!selection_text)
857    return false;
858
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      UTF8ToUTF16(selection_text), false, false, &match, NULL);
864  g_free(selection_text);
865  if (!match.destination_url.is_valid())
866    return false;
867
868  *url = match.destination_url;
869  return true;
870}
871
872bool AddWindowAlphaChannel(GtkWidget* window) {
873  GdkScreen* screen = gtk_widget_get_screen(window);
874  GdkColormap* rgba = gdk_screen_get_rgba_colormap(screen);
875  if (rgba)
876    gtk_widget_set_colormap(window, rgba);
877
878  return rgba;
879}
880
881void 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);
887
888  if (normal_base)
889    *normal_base = style->base[GTK_STATE_NORMAL];
890  if (selected_base)
891    *selected_base = style->base[GTK_STATE_SELECTED];
892  if (normal_text)
893    *normal_text = style->text[GTK_STATE_NORMAL];
894  if (selected_text)
895    *selected_text = style->text[GTK_STATE_SELECTED];
896
897  g_object_ref_sink(fake_entry);
898  g_object_unref(fake_entry);
899}
900
901void ShowDialog(GtkWidget* dialog) {
902  gtk_widget_show_all(dialog);
903}
904
905void ShowDialogWithLocalizedSize(GtkWidget* dialog,
906                                 int width_id,
907                                 int height_id,
908                                 bool resizeable) {
909  gtk_widget_realize(dialog);
910  SetWindowSizeFromResources(GTK_WINDOW(dialog),
911                             width_id,
912                             height_id,
913                             resizeable);
914  gtk_widget_show_all(dialog);
915}
916
917void ShowDialogWithMinLocalizedWidth(GtkWidget* dialog,
918                                     int width_id) {
919  gtk_widget_show_all(dialog);
920
921  // Suggest a minimum size.
922  gint width;
923  GtkRequisition req;
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);
928}
929
930void PresentWindow(GtkWidget* window, int timestamp) {
931  if (timestamp)
932    gtk_window_present_with_time(GTK_WINDOW(window), timestamp);
933  else
934    gtk_window_present(GTK_WINDOW(window));
935}
936
937gfx::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);
941
942  return gfx::Rect(x, y, width, height);
943}
944
945base::string16 GetStockPreferencesMenuLabel() {
946  GtkStockItem stock_item;
947  base::string16 preferences;
948  if (gtk_stock_lookup(GTK_STOCK_PREFERENCES, &stock_item)) {
949    const char16 kUnderscore[] = { '_', 0 };
950    base::RemoveChars(UTF8ToUTF16(stock_item.label), kUnderscore, &preferences);
951  }
952  return preferences;
953}
954
955bool IsWidgetAncestryVisible(GtkWidget* widget) {
956  GtkWidget* parent = widget;
957  while (parent && gtk_widget_get_visible(parent))
958    parent = gtk_widget_get_parent(parent);
959  return !parent;
960}
961
962void SetLabelWidth(GtkWidget* label, int pixel_width) {
963  gtk_label_set_line_wrap(GTK_LABEL(label), TRUE);
964  gtk_misc_set_alignment(GTK_MISC(label), 0, 0.5);
965
966  // Do the simple thing in LTR because the bug only affects right-aligned
967  // text. Also, when using the workaround, the label tries to maintain
968  // uniform line-length, which we don't really want.
969  if (gtk_widget_get_direction(label) == GTK_TEXT_DIR_LTR) {
970    gtk_widget_set_size_request(label, pixel_width, -1);
971  } else {
972    // The label has to be realized before we can adjust its width.
973    if (gtk_widget_get_realized(label)) {
974      OnLabelRealize(label, GINT_TO_POINTER(pixel_width));
975    } else {
976      g_signal_connect(label, "realize", G_CALLBACK(OnLabelRealize),
977                       GINT_TO_POINTER(pixel_width));
978    }
979  }
980}
981
982void InitLabelSizeRequestAndEllipsizeMode(GtkWidget* label) {
983  GtkRequisition size;
984  gtk_label_set_ellipsize(GTK_LABEL(label), PANGO_ELLIPSIZE_NONE);
985  gtk_widget_set_size_request(label, -1, -1);
986  gtk_widget_size_request(label, &size);
987  gtk_widget_set_size_request(label, size.width, size.height);
988  gtk_label_set_ellipsize(GTK_LABEL(label), PANGO_ELLIPSIZE_END);
989}
990
991void ApplyMessageDialogQuirks(GtkWidget* dialog) {
992  if (gtk_window_get_modal(GTK_WINDOW(dialog))) {
993    // Work around a KDE 3 window manager bug.
994    scoped_ptr<base::Environment> env(base::Environment::Create());
995    if (base::nix::DESKTOP_ENVIRONMENT_KDE3 ==
996        base::nix::GetDesktopEnvironment(env.get()))
997      gtk_window_set_skip_taskbar_hint(GTK_WINDOW(dialog), FALSE);
998  }
999}
1000
1001}  // namespace gtk_util
1002