root/chrome/browser/ui/gtk/gtk_util.cc

/* [<][>][^][v][top][bottom][index][help] */

DEFINITIONS

This source file includes following definitions.
  1. RemoveWidget
  2. OnMouseButtonPressed
  3. OnMouseButtonReleased
  4. GetCharacterWidthForPixels
  5. OnLabelRealize
  6. GetIconList
  7. GetAvatarIcon
  8. GetChromeIcon
  9. AddEmblem
  10. GetIconListWithAvatars
  11. PaintNoBackground
  12. CreateLabeledControlsGroup
  13. CreateGtkBorderBin
  14. LeftAlignMisc
  15. CreateBoldLabel
  16. GetWidgetSizeFromCharacters
  17. GetWidgetSizeFromResources
  18. SetWindowSizeFromResources
  19. MakeAppModalWindowGroup
  20. AppModalDismissedUngroupWindows
  21. RemoveAllChildren
  22. ForceFontSizePixels
  23. UndoForceFontSize
  24. GetWidgetSize
  25. ConvertWidgetPointToScreen
  26. CenterWidgetInHBox
  27. SetButtonClickableByMouseButtons
  28. SetButtonTriggersNavigation
  29. MirroredLeftPointForRect
  30. MirroredRightPointForRect
  31. MirroredXCoordinate
  32. WidgetContainsCursor
  33. SetDefaultWindowIcon
  34. SetWindowIcon
  35. SetWindowIcon
  36. AddButtonToDialog
  37. BuildDialogButton
  38. CreateEntryImageHBox
  39. SetLabelColor
  40. IndentWidget
  41. MakeBidiGdkPoint
  42. BuildTooltipTitleFor
  43. DrawTextEntryBackground
  44. SetLayoutText
  45. DrawThemedToolbarBackground
  46. DrawFullImage
  47. AverageColors
  48. SetAlwaysShowImage
  49. GetWidgetRectRelativeToToplevel
  50. SuppressDefaultPainting
  51. GrabAllInput
  52. WidgetBounds
  53. SetWMLastUserActionTime
  54. XTimeNow
  55. URLFromPrimarySelection
  56. AddWindowAlphaChannel
  57. GetTextColors
  58. ShowDialog
  59. ShowDialogWithLocalizedSize
  60. ShowDialogWithMinLocalizedWidth
  61. PresentWindow
  62. GetDialogBounds
  63. GetStockPreferencesMenuLabel
  64. IsWidgetAncestryVisible
  65. SetLabelWidth
  66. InitLabelSizeRequestAndEllipsizeMode
  67. ApplyMessageDialogQuirks

// Copyright (c) 2012 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#include "chrome/browser/ui/gtk/gtk_util.h"

#include <cairo/cairo.h>

#include <algorithm>
#include <cstdarg>
#include <map>

#include "base/environment.h"
#include "base/i18n/rtl.h"
#include "base/logging.h"
#include "base/nix/xdg_util.h"
#include "base/strings/string_number_conversions.h"
#include "base/strings/utf_string_conversions.h"
#include "chrome/browser/autocomplete/autocomplete_classifier.h"
#include "chrome/browser/autocomplete/autocomplete_classifier_factory.h"
#include "chrome/browser/autocomplete/autocomplete_input.h"
#include "chrome/browser/autocomplete/autocomplete_match.h"
#include "chrome/browser/browser_process.h"
#include "chrome/browser/profiles/profile.h"
#include "chrome/browser/profiles/profile_info_cache.h"
#include "chrome/browser/profiles/profile_manager.h"
#include "chrome/browser/profiles/profiles_state.h"
#include "chrome/browser/themes/theme_properties.h"
#include "chrome/browser/ui/browser.h"
#include "chrome/browser/ui/browser_iterator.h"
#include "chrome/browser/ui/browser_list.h"
#include "chrome/browser/ui/browser_window.h"
#include "chrome/browser/ui/gtk/browser_window_gtk.h"
#include "chrome/browser/ui/gtk/gtk_theme_service.h"
#include "chrome/browser/ui/host_desktop.h"
#include "grit/chrome_unscaled_resources.h"
#include "grit/theme_resources.h"
#include "ui/base/accelerators/menu_label_accelerator_util_linux.h"
#include "ui/base/gtk/gtk_hig_constants.h"
#include "ui/base/gtk/gtk_screen_util.h"
#include "ui/base/l10n/l10n_util.h"
#include "ui/base/resource/resource_bundle.h"
#include "ui/base/x/x11_util.h"
#include "ui/gfx/gtk_compat.h"
#include "ui/gfx/image/cairo_cached_surface.h"
#include "ui/gfx/image/image.h"
#include "ui/gfx/pango_util.h"
#include "ui/gfx/text_elider.h"
#include "url/gurl.h"

// These conflict with base/tracked_objects.h, so need to come last.
#include <gdk/gdkx.h>  // NOLINT

namespace {

#if defined(GOOGLE_CHROME_BUILD)
static const char* kIconName = "google-chrome";
#else
static const char* kIconName = "chromium-browser";
#endif

const char kBoldLabelMarkup[] = "<span weight='bold'>%s</span>";

// Max size of each component of the button tooltips.
const size_t kMaxTooltipTitleLength = 100;
const size_t kMaxTooltipURLLength = 400;

// Callback used in RemoveAllChildren.
void RemoveWidget(GtkWidget* widget, gpointer container) {
  gtk_container_remove(GTK_CONTAINER(container), widget);
}

// These two functions are copped almost directly from gtk core. The only
// difference is that they accept middle clicks.
gboolean OnMouseButtonPressed(GtkWidget* widget, GdkEventButton* event,
                              gpointer userdata) {
  if (event->type == GDK_BUTTON_PRESS) {
    if (gtk_button_get_focus_on_click(GTK_BUTTON(widget)) &&
        !gtk_widget_has_focus(widget)) {
      gtk_widget_grab_focus(widget);
    }

    gint button_mask = GPOINTER_TO_INT(userdata);
    if (button_mask & (1 << event->button))
      gtk_button_pressed(GTK_BUTTON(widget));
  }

  return TRUE;
}

gboolean OnMouseButtonReleased(GtkWidget* widget, GdkEventButton* event,
                               gpointer userdata) {
  gint button_mask = GPOINTER_TO_INT(userdata);
  if (button_mask && (1 << event->button))
    gtk_button_released(GTK_BUTTON(widget));

  return TRUE;
}

// Returns the approximate number of characters that can horizontally fit in
// |pixel_width| pixels.
int GetCharacterWidthForPixels(GtkWidget* widget, int pixel_width) {
  DCHECK(gtk_widget_get_realized(widget))
      << " widget must be realized to compute font metrics correctly";

  PangoContext* context = gtk_widget_create_pango_context(widget);
  GtkStyle* style = gtk_widget_get_style(widget);
  PangoFontMetrics* metrics = pango_context_get_metrics(context,
      style->font_desc, pango_context_get_language(context));

  // This technique (max of char and digit widths) matches the code in
  // gtklabel.c.
  int char_width = pixel_width * PANGO_SCALE /
      std::max(pango_font_metrics_get_approximate_char_width(metrics),
               pango_font_metrics_get_approximate_digit_width(metrics));

  pango_font_metrics_unref(metrics);
  g_object_unref(context);

  return char_width;
}

void OnLabelRealize(GtkWidget* label, gpointer pixel_width) {
  gtk_label_set_width_chars(
      GTK_LABEL(label),
      GetCharacterWidthForPixels(label, GPOINTER_TO_INT(pixel_width)));
}

// Ownership of |icon_list| is passed to the caller.
GList* GetIconList() {
  ui::ResourceBundle& rb = ui::ResourceBundle::GetSharedInstance();
  GList* icon_list = NULL;
  icon_list = g_list_append(icon_list,
      rb.GetNativeImageNamed(IDR_PRODUCT_LOGO_32).ToGdkPixbuf());
  icon_list = g_list_append(icon_list,
      rb.GetNativeImageNamed(IDR_PRODUCT_LOGO_16).ToGdkPixbuf());
  return icon_list;
}

// Returns the avatar icon for |profile|.
//
// Returns NULL if there is only one profile; always returns an icon for
// Incognito profiles.
//
// The returned pixbuf must not be unreferenced or freed because it's owned by
// either the resource bundle or the profile info cache.
GdkPixbuf* GetAvatarIcon(Profile* profile) {
  if (profile->IsOffTheRecord()) {
    ui::ResourceBundle& rb = ui::ResourceBundle::GetSharedInstance();
    return rb.GetNativeImageNamed(IDR_OTR_ICON).ToGdkPixbuf();
  }

  const ProfileInfoCache& cache =
      g_browser_process->profile_manager()->GetProfileInfoCache();

  if (!profiles::IsMultipleProfilesEnabled() ||
      cache.GetNumberOfProfiles() < 2)
    return NULL;

  const size_t index = cache.GetIndexOfProfileWithPath(profile->GetPath());

  return (index != std::string::npos ?
          cache.GetAvatarIconOfProfileAtIndex(index).ToGdkPixbuf() :
          static_cast<GdkPixbuf*>(NULL));
}

// Gets the Chrome product icon.
//
// If it doesn't find the icon in |theme|, it looks among the icons packaged
// with Chrome.
//
// Supported values of |size| are 16, 32, and 64. If the Chrome icon is found
// in |theme|, the returned icon may not be of the requested size if |size|
// has an unsupported value (GTK might scale it). If the Chrome icon is not
// found in |theme|, and |size| has an unsupported value, the program will be
// aborted with CHECK(false).
//
// The caller is responsible for calling g_object_unref() on the returned
// pixbuf.
GdkPixbuf* GetChromeIcon(GtkIconTheme* theme, const int size) {
  if (gtk_icon_theme_has_icon(theme, kIconName)) {
    GdkPixbuf* icon =
        gtk_icon_theme_load_icon(theme,
                                 kIconName,
                                 size,
                                 static_cast<GtkIconLookupFlags>(0),
                                 0);
    GdkPixbuf* icon_copy = gdk_pixbuf_copy(icon);
    g_object_unref(icon);
    return icon_copy;
  }

  ui::ResourceBundle& rb = ui::ResourceBundle::GetSharedInstance();
  int id = 0;

  switch (size) {
    case 16: id = IDR_PRODUCT_LOGO_16; break;
    case 32: id = IDR_PRODUCT_LOGO_32; break;
    case 64: id = IDR_PRODUCT_LOGO_64; break;
    default: CHECK(false); break;
  }

  return gdk_pixbuf_copy(rb.GetNativeImageNamed(id).ToGdkPixbuf());
}

// Adds |emblem| to the bottom-right corner of |icon|.
//
// Taking the ceiling of the scaled destination rect's dimensions (|dest_w|
// and |dest_h|) because, if the destination rect is larger than the scaled
// emblem, gdk_pixbuf_composite() will replicate the edge pixels of the emblem
// to fill the gap, which is better than a cropped emblem, I think.
void AddEmblem(const GdkPixbuf* emblem, GdkPixbuf* icon) {
  const int iw = gdk_pixbuf_get_width(icon);
  const int ih = gdk_pixbuf_get_height(icon);
  const int ew = gdk_pixbuf_get_width(emblem);
  const int eh = gdk_pixbuf_get_height(emblem);

  const double emblem_scale =
      (static_cast<double>(ih) / static_cast<double>(eh)) * 0.5;
  const int dest_w = ::ceil(ew * emblem_scale);
  const int dest_h = ::ceil(eh * emblem_scale);
  const int x = iw - dest_w;  // Used for offset_x and dest_x.
  const int y = ih - dest_h;  // Used for offset_y and dest_y.

  gdk_pixbuf_composite(emblem, icon,
                       x, y,
                       dest_w, dest_h,
                       x, y,
                       emblem_scale, emblem_scale,
                       GDK_INTERP_BILINEAR, 255);
}

// Returns a list containing Chrome icons of various sizes emblemed with the
// |profile|'s avatar.
//
// If there is only one profile, no emblem is added, but icons for Incognito
// profiles will always get the Incognito emblem.
//
// The caller owns the list and all the icons it contains will have had their
// reference counts incremented. Therefore the caller should unreference each
// element before freeing the list.
GList* GetIconListWithAvatars(GtkWindow* window, Profile* profile) {
  GtkIconTheme* theme =
      gtk_icon_theme_get_for_screen(gtk_widget_get_screen(GTK_WIDGET(window)));

  GdkPixbuf* icon_16 = GetChromeIcon(theme, 16);
  GdkPixbuf* icon_32 = GetChromeIcon(theme, 32);
  GdkPixbuf* icon_64 = GetChromeIcon(theme, 64);

  const GdkPixbuf* avatar = GetAvatarIcon(profile);
  if (avatar) {
    AddEmblem(avatar, icon_16);
    AddEmblem(avatar, icon_32);
    AddEmblem(avatar, icon_64);
  }

  GList* icon_list = NULL;
  icon_list = g_list_append(icon_list, icon_64);
  icon_list = g_list_append(icon_list, icon_32);
  icon_list = g_list_append(icon_list, icon_16);

  return icon_list;
}

// Expose event handler for a container that simply suppresses the default
// drawing and propagates the expose event to the container's children.
gboolean PaintNoBackground(GtkWidget* widget,
                           GdkEventExpose* event,
                           gpointer unused) {
  GList* children = gtk_container_get_children(GTK_CONTAINER(widget));
  for (GList* item = children; item; item = item->next) {
    gtk_container_propagate_expose(GTK_CONTAINER(widget),
                                   GTK_WIDGET(item->data),
                                   event);
  }
  g_list_free(children);

  return TRUE;
}

}  // namespace

namespace gtk_util {

GtkWidget* CreateLabeledControlsGroup(std::vector<GtkWidget*>* labels,
                                      const char* text, ...) {
  va_list ap;
  va_start(ap, text);
  GtkWidget* table = gtk_table_new(0, 2, FALSE);
  gtk_table_set_col_spacing(GTK_TABLE(table), 0, ui::kLabelSpacing);
  gtk_table_set_row_spacings(GTK_TABLE(table), ui::kControlSpacing);

  for (guint row = 0; text; ++row) {
    gtk_table_resize(GTK_TABLE(table), row + 1, 2);
    GtkWidget* control = va_arg(ap, GtkWidget*);
    GtkWidget* label = gtk_label_new(text);
    gtk_misc_set_alignment(GTK_MISC(label), 0, 0.5);
    if (labels)
      labels->push_back(label);

    gtk_table_attach(GTK_TABLE(table), label,
                 0, 1, row, row + 1,
                 GTK_FILL, GTK_FILL,
                 0, 0);
    gtk_table_attach_defaults(GTK_TABLE(table), control,
                              1, 2, row, row + 1);
    text = va_arg(ap, const char*);
  }
  va_end(ap);

  return table;
}

GtkWidget* CreateGtkBorderBin(GtkWidget* child, const GdkColor* color,
                              int top, int bottom, int left, int right) {
  // Use a GtkEventBox to get the background painted.  However, we can't just
  // use a container border, since it won't paint there.  Use an alignment
  // inside to get the sizes exactly of how we want the border painted.
  GtkWidget* ebox = gtk_event_box_new();
  if (color)
    gtk_widget_modify_bg(ebox, GTK_STATE_NORMAL, color);
  GtkWidget* alignment = gtk_alignment_new(0.0, 0.0, 1.0, 1.0);
  gtk_alignment_set_padding(GTK_ALIGNMENT(alignment), top, bottom, left, right);
  gtk_container_add(GTK_CONTAINER(alignment), child);
  gtk_container_add(GTK_CONTAINER(ebox), alignment);
  return ebox;
}

GtkWidget* LeftAlignMisc(GtkWidget* misc) {
  gtk_misc_set_alignment(GTK_MISC(misc), 0, 0.5);
  return misc;
}

GtkWidget* CreateBoldLabel(const std::string& text) {
  GtkWidget* label = gtk_label_new(NULL);
  char* markup = g_markup_printf_escaped(kBoldLabelMarkup, text.c_str());
  gtk_label_set_markup(GTK_LABEL(label), markup);
  g_free(markup);

  return LeftAlignMisc(label);
}

void GetWidgetSizeFromCharacters(
    GtkWidget* widget, double width_chars, double height_lines,
    int* width, int* height) {
  DCHECK(gtk_widget_get_realized(widget))
      << " widget must be realized to compute font metrics correctly";
  PangoContext* context = gtk_widget_create_pango_context(widget);
  GtkStyle* style = gtk_widget_get_style(widget);
  PangoFontMetrics* metrics = pango_context_get_metrics(context,
      style->font_desc, pango_context_get_language(context));
  if (width) {
    *width = static_cast<int>(
        pango_font_metrics_get_approximate_char_width(metrics) *
        width_chars / PANGO_SCALE);
  }
  if (height) {
    *height = static_cast<int>(
        (pango_font_metrics_get_ascent(metrics) +
        pango_font_metrics_get_descent(metrics)) *
        height_lines / PANGO_SCALE);
  }
  pango_font_metrics_unref(metrics);
  g_object_unref(context);
}

void GetWidgetSizeFromResources(
    GtkWidget* widget, int width_chars, int height_lines,
    int* width, int* height) {
  DCHECK(gtk_widget_get_realized(widget))
      << " widget must be realized to compute font metrics correctly";

  double chars = 0;
  if (width)
    base::StringToDouble(l10n_util::GetStringUTF8(width_chars), &chars);

  double lines = 0;
  if (height)
    base::StringToDouble(l10n_util::GetStringUTF8(height_lines), &lines);

  GetWidgetSizeFromCharacters(widget, chars, lines, width, height);
}

void SetWindowSizeFromResources(GtkWindow* window,
                                int width_id, int height_id, bool resizable) {
  int width = -1;
  int height = -1;
  gtk_util::GetWidgetSizeFromResources(GTK_WIDGET(window), width_id, height_id,
                                       (width_id != -1) ? &width : NULL,
                                       (height_id != -1) ? &height : NULL);

  if (resizable) {
    gtk_window_set_default_size(window, width, height);
  } else {
    // For a non-resizable window, GTK tries to snap the window size
    // to the minimum size around the content.  We use the sizes in
    // the resources to set *minimum* window size to allow windows
    // with long titles to be wide enough to display their titles.
    //
    // But if GTK wants to make the window *wider* due to very wide
    // controls, we should allow that too, so be careful to pick the
    // wider of the resources size and the natural window size.

    gtk_widget_show_all(gtk_bin_get_child(GTK_BIN(window)));
    GtkRequisition requisition;
    gtk_widget_size_request(GTK_WIDGET(window), &requisition);
    gtk_widget_set_size_request(
        GTK_WIDGET(window),
        width == -1 ? -1 : std::max(width, requisition.width),
        height == -1 ? -1 : std::max(height, requisition.height));
  }
  gtk_window_set_resizable(window, resizable ? TRUE : FALSE);
}

void MakeAppModalWindowGroup() {
  // Older versions of GTK+ don't give us gtk_window_group_list() which is what
  // we need to add current non-browser modal dialogs to the list. If
  // we have 2.14+ we can do things the correct way.
  GtkWindowGroup* window_group = gtk_window_group_new();
  for (chrome::BrowserIterator it; !it.done(); it.Next()) {
    // List all windows in this current group
    GtkWindowGroup* old_group =
        gtk_window_get_group((*it)->window()->GetNativeWindow());

    GList* all_windows = gtk_window_group_list_windows(old_group);
    for (GList* window = all_windows; window; window = window->next) {
      gtk_window_group_add_window(window_group, GTK_WINDOW(window->data));
    }
    g_list_free(all_windows);
  }
  g_object_unref(window_group);
}

void AppModalDismissedUngroupWindows() {
  // GTK only has the native desktop.
  const BrowserList* native_browser_list =
      BrowserList::GetInstance(chrome::HOST_DESKTOP_TYPE_NATIVE);
  if (!native_browser_list->empty()) {
    std::vector<GtkWindow*> transient_windows;

    // All windows should be part of one big modal group right now.
    GtkWindowGroup* window_group = gtk_window_get_group(
        native_browser_list->get(0)->window()->GetNativeWindow());
    GList* windows = gtk_window_group_list_windows(window_group);

    for (GList* item = windows; item; item = item->next) {
      GtkWindow* window = GTK_WINDOW(item->data);
      GtkWindow* transient_for = gtk_window_get_transient_for(window);
      if (transient_for) {
        transient_windows.push_back(window);
      } else {
        GtkWindowGroup* window_group = gtk_window_group_new();
        gtk_window_group_add_window(window_group, window);
        g_object_unref(window_group);
      }
    }

    // Put each transient window in the same group as its transient parent.
    for (std::vector<GtkWindow*>::iterator it = transient_windows.begin();
         it != transient_windows.end(); ++it) {
      GtkWindow* transient_parent = gtk_window_get_transient_for(*it);
      GtkWindowGroup* group = gtk_window_get_group(transient_parent);
      gtk_window_group_add_window(group, *it);
    }
    g_list_free(windows);
  }
}

void RemoveAllChildren(GtkWidget* container) {
  gtk_container_foreach(GTK_CONTAINER(container), RemoveWidget, container);
}

void ForceFontSizePixels(GtkWidget* widget, double size_pixels) {
  gfx::ScopedPangoFontDescription font_desc(pango_font_description_new());
  // pango_font_description_set_absolute_size sets the font size in device
  // units, which for us is pixels.
  pango_font_description_set_absolute_size(font_desc.get(),
                                           PANGO_SCALE * size_pixels);
  gtk_widget_modify_font(widget, font_desc.get());
}

void UndoForceFontSize(GtkWidget* widget) {
  gtk_widget_modify_font(widget, NULL);
}

gfx::Size GetWidgetSize(GtkWidget* widget) {
  GtkRequisition size;
  gtk_widget_size_request(widget, &size);
  return gfx::Size(size.width, size.height);
}

void ConvertWidgetPointToScreen(GtkWidget* widget, gfx::Point* p) {
  DCHECK(widget);
  DCHECK(p);

  *p += ui::GetWidgetScreenOffset(widget);
}

GtkWidget* CenterWidgetInHBox(GtkWidget* hbox, GtkWidget* widget,
                              bool pack_at_end, int padding) {
  GtkWidget* centering_vbox = gtk_vbox_new(FALSE, 0);
  gtk_box_pack_start(GTK_BOX(centering_vbox), widget, TRUE, FALSE, 0);
  if (pack_at_end)
    gtk_box_pack_end(GTK_BOX(hbox), centering_vbox, FALSE, FALSE, padding);
  else
    gtk_box_pack_start(GTK_BOX(hbox), centering_vbox, FALSE, FALSE, padding);

  return centering_vbox;
}

void SetButtonClickableByMouseButtons(GtkWidget* button,
                                      bool left, bool middle, bool right) {
  gint button_mask = 0;
  if (left)
    button_mask |= 1 << 1;
  if (middle)
    button_mask |= 1 << 2;
  if (right)
    button_mask |= 1 << 3;
  void* userdata = GINT_TO_POINTER(button_mask);

  g_signal_connect(button, "button-press-event",
                   G_CALLBACK(OnMouseButtonPressed), userdata);
  g_signal_connect(button, "button-release-event",
                   G_CALLBACK(OnMouseButtonReleased), userdata);
}

void SetButtonTriggersNavigation(GtkWidget* button) {
  SetButtonClickableByMouseButtons(button, true, true, false);
}

int MirroredLeftPointForRect(GtkWidget* widget, const gfx::Rect& bounds) {
  if (!base::i18n::IsRTL())
    return bounds.x();

  GtkAllocation allocation;
  gtk_widget_get_allocation(widget, &allocation);
  return allocation.width - bounds.x() - bounds.width();
}

int MirroredRightPointForRect(GtkWidget* widget, const gfx::Rect& bounds) {
  if (!base::i18n::IsRTL())
    return bounds.right();

  GtkAllocation allocation;
  gtk_widget_get_allocation(widget, &allocation);
  return allocation.width - bounds.x();
}

int MirroredXCoordinate(GtkWidget* widget, int x) {
  if (base::i18n::IsRTL()) {
    GtkAllocation allocation;
    gtk_widget_get_allocation(widget, &allocation);
    return allocation.width - x;
  }
  return x;
}

bool WidgetContainsCursor(GtkWidget* widget) {
  gint x = 0;
  gint y = 0;
  gtk_widget_get_pointer(widget, &x, &y);
  return WidgetBounds(widget).Contains(x, y);
}

void SetDefaultWindowIcon(GtkWindow* window) {
  GtkIconTheme* theme =
      gtk_icon_theme_get_for_screen(gtk_widget_get_screen(GTK_WIDGET(window)));

  if (gtk_icon_theme_has_icon(theme, kIconName)) {
    gtk_window_set_default_icon_name(kIconName);
    // Sometimes the WM fails to update the icon when we tell it to. The above
    // line should be enough to update all existing windows, but it can fail,
    // e.g. with Lucid/metacity. The following line seems to fix the common
    // case where the first window created doesn't have an icon.
    gtk_window_set_icon_name(window, kIconName);
  } else {
    GList* icon_list = GetIconList();
    gtk_window_set_default_icon_list(icon_list);
    // Same logic applies here.
    gtk_window_set_icon_list(window, icon_list);
    g_list_free(icon_list);
  }
}

void SetWindowIcon(GtkWindow* window, Profile* profile) {
  GList* icon_list = GetIconListWithAvatars(window, profile);
  gtk_window_set_icon_list(window, icon_list);
  g_list_foreach(icon_list, reinterpret_cast<GFunc>(g_object_unref), NULL);
  g_list_free(icon_list);
}

void SetWindowIcon(GtkWindow* window, Profile* profile, GdkPixbuf* icon) {
  const GdkPixbuf* avatar = GetAvatarIcon(profile);
  if (avatar) AddEmblem(avatar, icon);
  gtk_window_set_icon(window, icon);
}

GtkWidget* AddButtonToDialog(GtkWidget* dialog, const gchar* text,
                             const gchar* stock_id, gint response_id) {
  GtkWidget* button = gtk_button_new_with_label(text);
  gtk_button_set_image(GTK_BUTTON(button),
                       gtk_image_new_from_stock(stock_id,
                                                GTK_ICON_SIZE_BUTTON));
  gtk_dialog_add_action_widget(GTK_DIALOG(dialog), button,
                               response_id);
  return button;
}

GtkWidget* BuildDialogButton(GtkWidget* dialog, int ids_id,
                             const gchar* stock_id) {
  GtkWidget* button = gtk_button_new_with_mnemonic(
      ui::ConvertAcceleratorsFromWindowsStyle(
          l10n_util::GetStringUTF8(ids_id)).c_str());
  gtk_button_set_image(GTK_BUTTON(button),
                       gtk_image_new_from_stock(stock_id,
                                                GTK_ICON_SIZE_BUTTON));
  return button;
}

GtkWidget* CreateEntryImageHBox(GtkWidget* entry, GtkWidget* image) {
  GtkWidget* hbox = gtk_hbox_new(FALSE, ui::kControlSpacing);
  gtk_box_pack_start(GTK_BOX(hbox), entry, TRUE, TRUE, 0);
  gtk_box_pack_start(GTK_BOX(hbox), image, FALSE, FALSE, 0);
  return hbox;
}

void SetLabelColor(GtkWidget* label, const GdkColor* color) {
  gtk_widget_modify_fg(label, GTK_STATE_NORMAL, color);
  gtk_widget_modify_fg(label, GTK_STATE_ACTIVE, color);
  gtk_widget_modify_fg(label, GTK_STATE_PRELIGHT, color);
  gtk_widget_modify_fg(label, GTK_STATE_INSENSITIVE, color);
}

GtkWidget* IndentWidget(GtkWidget* content) {
  GtkWidget* content_alignment = gtk_alignment_new(0.0, 0.5, 1.0, 1.0);
  gtk_alignment_set_padding(GTK_ALIGNMENT(content_alignment), 0, 0,
                            ui::kGroupIndent, 0);
  gtk_container_add(GTK_CONTAINER(content_alignment), content);
  return content_alignment;
}

GdkPoint MakeBidiGdkPoint(gint x, gint y, gint width, bool ltr) {
  GdkPoint point = {ltr ? x : width - x, y};
  return point;
}

std::string BuildTooltipTitleFor(base::string16 title, const GURL& url) {
  const std::string& url_str = url.possibly_invalid_spec();
  const std::string& title_str = base::UTF16ToUTF8(title);

  std::string truncated_url = base::UTF16ToUTF8(gfx::TruncateString(
      base::UTF8ToUTF16(url_str), kMaxTooltipURLLength));
  gchar* escaped_url_cstr = g_markup_escape_text(truncated_url.c_str(),
                                                 truncated_url.size());
  std::string escaped_url(escaped_url_cstr);
  g_free(escaped_url_cstr);

  if (url_str == title_str || title.empty()) {
    return escaped_url;
  } else {
    std::string truncated_title = base::UTF16ToUTF8(gfx::TruncateString(
        title, kMaxTooltipTitleLength));
    gchar* escaped_title_cstr = g_markup_escape_text(truncated_title.c_str(),
                                                     truncated_title.size());
    std::string escaped_title(escaped_title_cstr);
    g_free(escaped_title_cstr);

    if (!escaped_url.empty())
      return std::string("<b>") + escaped_title + "</b>\n" + escaped_url;
    else
      return std::string("<b>") + escaped_title + "</b>";
  }
}

void DrawTextEntryBackground(GtkWidget* offscreen_entry,
                             GtkWidget* widget_to_draw_on,
                             GdkRectangle* dirty_rec,
                             GdkRectangle* rec) {
  GtkStyle* gtk_owned_style = gtk_rc_get_style(offscreen_entry);
  // GTK owns the above and we're going to have to make our own copy of it
  // that we can edit.
  GtkStyle* our_style = gtk_style_copy(gtk_owned_style);
  our_style = gtk_style_attach(our_style, widget_to_draw_on->window);

  // TODO(erg): Draw the focus ring if appropriate...

  // We're using GTK rendering; draw a GTK entry widget onto the background.
  gtk_paint_shadow(our_style, widget_to_draw_on->window,
                   GTK_STATE_NORMAL, GTK_SHADOW_IN, dirty_rec,
                   widget_to_draw_on, "entry",
                   rec->x, rec->y, rec->width, rec->height);

  // Draw the interior background (not all themes draw the entry background
  // above; this is a noop on themes that do).
  gint xborder = our_style->xthickness;
  gint yborder = our_style->ythickness;
  gint width = rec->width - 2 * xborder;
  gint height = rec->height - 2 * yborder;
  if (width > 0 && height > 0) {
    gtk_paint_flat_box(our_style, widget_to_draw_on->window,
                       GTK_STATE_NORMAL, GTK_SHADOW_NONE, dirty_rec,
                       widget_to_draw_on, "entry_bg",
                       rec->x + xborder, rec->y + yborder,
                       width, height);
  }

  gtk_style_detach(our_style);
  g_object_unref(our_style);
}

void SetLayoutText(PangoLayout* layout, const base::string16& text) {
  // Pango is really easy to overflow and send into a computational death
  // spiral that can corrupt the screen. Assume that we'll never have more than
  // 2000 characters, which should be a safe assumption until we all get robot
  // eyes. http://crbug.com/66576
  std::string text_utf8 = base::UTF16ToUTF8(text);
  if (text_utf8.length() > 2000)
    text_utf8 = text_utf8.substr(0, 2000);

  pango_layout_set_text(layout, text_utf8.data(), text_utf8.length());
}

void DrawThemedToolbarBackground(GtkWidget* widget,
                                 cairo_t* cr,
                                 GdkEventExpose* event,
                                 const gfx::Point& tabstrip_origin,
                                 GtkThemeService* theme_service) {
  // Fill the entire region with the toolbar color.
  GdkColor color = theme_service->GetGdkColor(
      ThemeProperties::COLOR_TOOLBAR);
  gdk_cairo_set_source_color(cr, &color);
  cairo_fill(cr);

  // The toolbar is supposed to blend in with the active tab, so we have to pass
  // coordinates for the IDR_THEME_TOOLBAR bitmap relative to the top of the
  // tab strip.
  const gfx::Image background =
      theme_service->GetImageNamed(IDR_THEME_TOOLBAR);
  background.ToCairo()->SetSource(cr, widget,
                                   tabstrip_origin.x(), tabstrip_origin.y());
  // We tile the toolbar background in both directions.
  cairo_pattern_set_extend(cairo_get_source(cr), CAIRO_EXTEND_REPEAT);
  cairo_rectangle(cr,
                  tabstrip_origin.x(),
                  tabstrip_origin.y(),
                  event->area.x + event->area.width - tabstrip_origin.x(),
                  event->area.y + event->area.height - tabstrip_origin.y());
  cairo_fill(cr);
}

void DrawFullImage(cairo_t* cr,
                   GtkWidget* widget,
                   const gfx::Image& image,
                   gint dest_x,
                   gint dest_y) {
  gfx::CairoCachedSurface* surface = image.ToCairo();
  surface->SetSource(cr, widget, dest_x, dest_y);
  cairo_pattern_set_extend(cairo_get_source(cr), CAIRO_EXTEND_REPEAT);
  cairo_rectangle(cr, dest_x, dest_y, surface->Width(), surface->Height());
  cairo_fill(cr);
}

GdkColor AverageColors(GdkColor color_one, GdkColor color_two) {
  GdkColor average_color;
  average_color.pixel = 0;
  average_color.red = (color_one.red + color_two.red) / 2;
  average_color.green = (color_one.green + color_two.green) / 2;
  average_color.blue = (color_one.blue + color_two.blue) / 2;
  return average_color;
}

void SetAlwaysShowImage(GtkWidget* image_menu_item) {
  gtk_image_menu_item_set_always_show_image(
      GTK_IMAGE_MENU_ITEM(image_menu_item), TRUE);
}

gfx::Rect GetWidgetRectRelativeToToplevel(GtkWidget* widget) {
  DCHECK(gtk_widget_get_realized(widget));

  GtkWidget* toplevel = gtk_widget_get_toplevel(widget);
  DCHECK(toplevel);
  DCHECK(gtk_widget_get_realized(toplevel));

  gint x = 0, y = 0;
  gtk_widget_translate_coordinates(widget,
                                   toplevel,
                                   0, 0,
                                   &x, &y);

  GtkAllocation allocation;
  gtk_widget_get_allocation(widget, &allocation);
  return gfx::Rect(x, y, allocation.width, allocation.height);
}

void SuppressDefaultPainting(GtkWidget* container) {
  g_signal_connect(container, "expose-event",
                   G_CALLBACK(PaintNoBackground), NULL);
}

bool GrabAllInput(GtkWidget* widget) {
  guint time = gtk_get_current_event_time();

  if (!gtk_widget_get_visible(widget))
    return false;

  GdkWindow* gdk_window = gtk_widget_get_window(widget);
  if (gdk_pointer_grab(gdk_window,
                       TRUE,
                       GdkEventMask(GDK_BUTTON_PRESS_MASK |
                                    GDK_BUTTON_RELEASE_MASK |
                                    GDK_ENTER_NOTIFY_MASK |
                                    GDK_LEAVE_NOTIFY_MASK |
                                    GDK_POINTER_MOTION_MASK),
                       NULL, NULL, time) != 0) {
    return false;
  }

  if (gdk_keyboard_grab(gdk_window, TRUE, time) != 0) {
    gdk_display_pointer_ungrab(gdk_drawable_get_display(gdk_window), time);
    return false;
  }

  gtk_grab_add(widget);
  return true;
}

gfx::Rect WidgetBounds(GtkWidget* widget) {
  // To quote the gtk docs:
  //
  //   Widget coordinates are a bit odd; for historical reasons, they are
  //   defined as widget->window coordinates for widgets that are not
  //   GTK_NO_WINDOW widgets, and are relative to allocation.x, allocation.y
  //   for widgets that are GTK_NO_WINDOW widgets.
  //
  // So the base is always (0,0).
  GtkAllocation allocation;
  gtk_widget_get_allocation(widget, &allocation);
  return gfx::Rect(0, 0, allocation.width, allocation.height);
}

void SetWMLastUserActionTime(GtkWindow* window) {
  gdk_x11_window_set_user_time(gtk_widget_get_window(GTK_WIDGET(window)),
                               XTimeNow());
}

guint32 XTimeNow() {
  struct timespec ts;
  clock_gettime(CLOCK_MONOTONIC, &ts);
  return ts.tv_sec * 1000 + ts.tv_nsec / 1000000;
}

bool URLFromPrimarySelection(Profile* profile, GURL* url) {
  GtkClipboard* clipboard = gtk_clipboard_get(GDK_SELECTION_PRIMARY);
  DCHECK(clipboard);
  gchar* selection_text = gtk_clipboard_wait_for_text(clipboard);
  if (!selection_text)
    return false;

  // Use autocomplete to clean up the text, going so far as to turn it into
  // a search query if necessary.
  AutocompleteMatch match;
  AutocompleteClassifierFactory::GetForProfile(profile)->Classify(
      base::UTF8ToUTF16(selection_text), false, false,
      AutocompleteInput::INVALID_SPEC, &match, NULL);
  g_free(selection_text);
  if (!match.destination_url.is_valid())
    return false;

  *url = match.destination_url;
  return true;
}

bool AddWindowAlphaChannel(GtkWidget* window) {
  GdkScreen* screen = gtk_widget_get_screen(window);
  GdkColormap* rgba = gdk_screen_get_rgba_colormap(screen);
  if (rgba)
    gtk_widget_set_colormap(window, rgba);

  return rgba;
}

void GetTextColors(GdkColor* normal_base,
                   GdkColor* selected_base,
                   GdkColor* normal_text,
                   GdkColor* selected_text) {
  GtkWidget* fake_entry = gtk_entry_new();
  GtkStyle* style = gtk_rc_get_style(fake_entry);

  if (normal_base)
    *normal_base = style->base[GTK_STATE_NORMAL];
  if (selected_base)
    *selected_base = style->base[GTK_STATE_SELECTED];
  if (normal_text)
    *normal_text = style->text[GTK_STATE_NORMAL];
  if (selected_text)
    *selected_text = style->text[GTK_STATE_SELECTED];

  g_object_ref_sink(fake_entry);
  g_object_unref(fake_entry);
}

void ShowDialog(GtkWidget* dialog) {
  gtk_widget_show_all(dialog);
}

void ShowDialogWithLocalizedSize(GtkWidget* dialog,
                                 int width_id,
                                 int height_id,
                                 bool resizeable) {
  gtk_widget_realize(dialog);
  SetWindowSizeFromResources(GTK_WINDOW(dialog),
                             width_id,
                             height_id,
                             resizeable);
  gtk_widget_show_all(dialog);
}

void ShowDialogWithMinLocalizedWidth(GtkWidget* dialog,
                                     int width_id) {
  gtk_widget_show_all(dialog);

  // Suggest a minimum size.
  gint width;
  GtkRequisition req;
  gtk_widget_size_request(dialog, &req);
  gtk_util::GetWidgetSizeFromResources(dialog, width_id, 0, &width, NULL);
  if (width > req.width)
    gtk_widget_set_size_request(dialog, width, -1);
}

void PresentWindow(GtkWidget* window, int timestamp) {
  if (timestamp)
    gtk_window_present_with_time(GTK_WINDOW(window), timestamp);
  else
    gtk_window_present(GTK_WINDOW(window));
}

gfx::Rect GetDialogBounds(GtkWidget* dialog) {
  gint x = 0, y = 0, width = 1, height = 1;
  gtk_window_get_position(GTK_WINDOW(dialog), &x, &y);
  gtk_window_get_size(GTK_WINDOW(dialog), &width, &height);

  return gfx::Rect(x, y, width, height);
}

base::string16 GetStockPreferencesMenuLabel() {
  GtkStockItem stock_item;
  base::string16 preferences;
  if (gtk_stock_lookup(GTK_STOCK_PREFERENCES, &stock_item)) {
    const base::char16 kUnderscore[] = { '_', 0 };
    base::RemoveChars(base::UTF8ToUTF16(stock_item.label),
                      kUnderscore, &preferences);
  }
  return preferences;
}

bool IsWidgetAncestryVisible(GtkWidget* widget) {
  GtkWidget* parent = widget;
  while (parent && gtk_widget_get_visible(parent))
    parent = gtk_widget_get_parent(parent);
  return !parent;
}

void SetLabelWidth(GtkWidget* label, int pixel_width) {
  gtk_label_set_line_wrap(GTK_LABEL(label), TRUE);
  gtk_misc_set_alignment(GTK_MISC(label), 0, 0.5);

  // Do the simple thing in LTR because the bug only affects right-aligned
  // text. Also, when using the workaround, the label tries to maintain
  // uniform line-length, which we don't really want.
  if (gtk_widget_get_direction(label) == GTK_TEXT_DIR_LTR) {
    gtk_widget_set_size_request(label, pixel_width, -1);
  } else {
    // The label has to be realized before we can adjust its width.
    if (gtk_widget_get_realized(label)) {
      OnLabelRealize(label, GINT_TO_POINTER(pixel_width));
    } else {
      g_signal_connect(label, "realize", G_CALLBACK(OnLabelRealize),
                       GINT_TO_POINTER(pixel_width));
    }
  }
}

void InitLabelSizeRequestAndEllipsizeMode(GtkWidget* label) {
  GtkRequisition size;
  gtk_label_set_ellipsize(GTK_LABEL(label), PANGO_ELLIPSIZE_NONE);
  gtk_widget_set_size_request(label, -1, -1);
  gtk_widget_size_request(label, &size);
  gtk_widget_set_size_request(label, size.width, size.height);
  gtk_label_set_ellipsize(GTK_LABEL(label), PANGO_ELLIPSIZE_END);
}

void ApplyMessageDialogQuirks(GtkWidget* dialog) {
  if (gtk_window_get_modal(GTK_WINDOW(dialog))) {
    // Work around a KDE 3 window manager bug.
    scoped_ptr<base::Environment> env(base::Environment::Create());
    if (base::nix::DESKTOP_ENVIRONMENT_KDE3 ==
        base::nix::GetDesktopEnvironment(env.get()))
      gtk_window_set_skip_taskbar_hint(GTK_WINDOW(dialog), FALSE);
  }
}

}  // namespace gtk_util

/* [<][>][^][v][top][bottom][index][help] */