Home | History | Annotate | Download | only in gtk
      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_compat.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/text/text_elider.h"
     44 #include "ui/base/x/x11_util.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 "url/gurl.h"
     49 
     50 // These conflict with base/tracked_objects.h, so need to come last.
     51 #include <gdk/gdkx.h>  // NOLINT
     52 
     53 namespace {
     54 
     55 #if defined(GOOGLE_CHROME_BUILD)
     56 static const char* kIconName = "google-chrome";
     57 #else
     58 static const char* kIconName = "chromium-browser";
     59 #endif
     60 
     61 const char kBoldLabelMarkup[] = "<span weight='bold'>%s</span>";
     62 
     63 // Max size of each component of the button tooltips.
     64 const size_t kMaxTooltipTitleLength = 100;
     65 const size_t kMaxTooltipURLLength = 400;
     66 
     67 // Callback used in RemoveAllChildren.
     68 void 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.
     74 gboolean 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 
     90 gboolean 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.
    101 int GetCharacterWidthForPixels(GtkWidget* widget, int pixel_width) {
    102   DCHECK(gtk_widget_get_realized(widget))
    103       << " widget must be realized to compute font metrics correctly";
    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 
    122 void 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.
    129 GList* GetIconList() {
    130   ui::ResourceBundle& rb = ui::ResourceBundle::GetSharedInstance();
    131   GList* icon_list = NULL;
    132   icon_list = g_list_append(icon_list,
    133       rb.GetNativeImageNamed(IDR_PRODUCT_LOGO_32).ToGdkPixbuf());
    134   icon_list = g_list_append(icon_list,
    135       rb.GetNativeImageNamed(IDR_PRODUCT_LOGO_16).ToGdkPixbuf());
    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.
    146 GdkPixbuf* 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.
    179 GdkPixbuf* 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.
    211 void AddEmblem(const GdkPixbuf* emblem, GdkPixbuf* icon) {
    212   const int iw = gdk_pixbuf_get_width(icon);
    213   const int ih = gdk_pixbuf_get_height(icon);
    214   const int ew = gdk_pixbuf_get_width(emblem);
    215   const int eh = gdk_pixbuf_get_height(emblem);
    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.
    241 GList* 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.
    266 gboolean 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 
    282 namespace gtk_util {
    283 
    284 GtkWidget* 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 
    313 GtkWidget* CreateGtkBorderBin(GtkWidget* child, const GdkColor* color,
    314                               int top, int bottom, int left, int right) {
    315   // Use a GtkEventBox to get the background painted.  However, we can't just
    316   // use a container border, since it won't paint there.  Use an alignment
    317   // inside to get the sizes exactly of how we want the border painted.
    318   GtkWidget* ebox = gtk_event_box_new();
    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 
    328 GtkWidget* LeftAlignMisc(GtkWidget* misc) {
    329   gtk_misc_set_alignment(GTK_MISC(misc), 0, 0.5);
    330   return misc;
    331 }
    332 
    333 GtkWidget* CreateBoldLabel(const std::string& text) {
    334   GtkWidget* label = gtk_label_new(NULL);
    335   char* markup = g_markup_printf_escaped(kBoldLabelMarkup, text.c_str());
    336   gtk_label_set_markup(GTK_LABEL(label), markup);
    337   g_free(markup);
    338 
    339   return LeftAlignMisc(label);
    340 }
    341 
    342 void GetWidgetSizeFromCharacters(
    343     GtkWidget* widget, double width_chars, double height_lines,
    344     int* width, int* height) {
    345   DCHECK(gtk_widget_get_realized(widget))
    346       << " widget must be realized to compute font metrics correctly";
    347   PangoContext* context = gtk_widget_create_pango_context(widget);
    348   GtkStyle* style = gtk_widget_get_style(widget);
    349   PangoFontMetrics* metrics = pango_context_get_metrics(context,
    350       style->font_desc, pango_context_get_language(context));
    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 
    366 void GetWidgetSizeFromResources(
    367     GtkWidget* widget, int width_chars, int height_lines,
    368     int* width, int* height) {
    369   DCHECK(gtk_widget_get_realized(widget))
    370       << " widget must be realized to compute font metrics correctly";
    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 
    383 void 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 
    414 void MakeAppModalWindowGroup() {
    415   // Older versions of GTK+ don't give us gtk_window_group_list() which is what
    416   // we need to add current non-browser modal dialogs to the list. If
    417   // we have 2.14+ we can do things the correct way.
    418   GtkWindowGroup* window_group = gtk_window_group_new();
    419   for (chrome::BrowserIterator it; !it.done(); it.Next()) {
    420     // List all windows in this current group
    421     GtkWindowGroup* old_group =
    422         gtk_window_get_group((*it)->window()->GetNativeWindow());
    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 
    433 void AppModalDismissedUngroupWindows() {
    434   // GTK only has the native desktop.
    435   const BrowserList* native_browser_list =
    436       BrowserList::GetInstance(chrome::HOST_DESKTOP_TYPE_NATIVE);
    437   if (!native_browser_list->empty()) {
    438     std::vector<GtkWindow*> transient_windows;
    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 
    468 void RemoveAllChildren(GtkWidget* container) {
    469   gtk_container_foreach(GTK_CONTAINER(container), RemoveWidget, container);
    470 }
    471 
    472 void ForceFontSizePixels(GtkWidget* widget, double size_pixels) {
    473   gfx::ScopedPangoFontDescription font_desc(pango_font_description_new());
    474   // pango_font_description_set_absolute_size sets the font size in device
    475   // units, which for us is pixels.
    476   pango_font_description_set_absolute_size(font_desc.get(),
    477                                            PANGO_SCALE * size_pixels);
    478   gtk_widget_modify_font(widget, font_desc.get());
    479 }
    480 
    481 void UndoForceFontSize(GtkWidget* widget) {
    482   gtk_widget_modify_font(widget, NULL);
    483 }
    484 
    485 gfx::Size GetWidgetSize(GtkWidget* widget) {
    486   GtkRequisition size;
    487   gtk_widget_size_request(widget, &size);
    488   return gfx::Size(size.width, size.height);
    489 }
    490 
    491 void ConvertWidgetPointToScreen(GtkWidget* widget, gfx::Point* p) {
    492   DCHECK(widget);
    493   DCHECK(p);
    494 
    495   *p += ui::GetWidgetScreenOffset(widget);
    496 }
    497 
    498 GtkWidget* CenterWidgetInHBox(GtkWidget* hbox, GtkWidget* widget,
    499                               bool pack_at_end, int padding) {
    500   GtkWidget* centering_vbox = gtk_vbox_new(FALSE, 0);
    501   gtk_box_pack_start(GTK_BOX(centering_vbox), widget, TRUE, FALSE, 0);
    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 
    510 void 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 
    527 void SetButtonTriggersNavigation(GtkWidget* button) {
    528   SetButtonClickableByMouseButtons(button, true, true, false);
    529 }
    530 
    531 int 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 
    540 int 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 
    549 int MirroredXCoordinate(GtkWidget* widget, int x) {
    550   if (base::i18n::IsRTL()) {
    551     GtkAllocation allocation;
    552     gtk_widget_get_allocation(widget, &allocation);
    553     return allocation.width - x;
    554   }
    555   return x;
    556 }
    557 
    558 bool 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 
    565 void 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 
    585 void SetWindowIcon(GtkWindow* window, Profile* profile) {
    586   GList* icon_list = GetIconListWithAvatars(window, profile);
    587   gtk_window_set_icon_list(window, icon_list);
    588   g_list_foreach(icon_list, reinterpret_cast<GFunc>(g_object_unref), NULL);
    589   g_list_free(icon_list);
    590 }
    591 
    592 void SetWindowIcon(GtkWindow* window, Profile* profile, GdkPixbuf* icon) {
    593   const GdkPixbuf* avatar = GetAvatarIcon(profile);
    594   if (avatar) AddEmblem(avatar, icon);
    595   gtk_window_set_icon(window, icon);
    596 }
    597 
    598 GtkWidget* AddButtonToDialog(GtkWidget* dialog, const gchar* text,
    599                              const gchar* stock_id, gint response_id) {
    600   GtkWidget* button = gtk_button_new_with_label(text);
    601   gtk_button_set_image(GTK_BUTTON(button),
    602                        gtk_image_new_from_stock(stock_id,
    603                                                 GTK_ICON_SIZE_BUTTON));
    604   gtk_dialog_add_action_widget(GTK_DIALOG(dialog), button,
    605                                response_id);
    606   return button;
    607 }
    608 
    609 GtkWidget* BuildDialogButton(GtkWidget* dialog, int ids_id,
    610                              const gchar* stock_id) {
    611   GtkWidget* button = gtk_button_new_with_mnemonic(
    612       ui::ConvertAcceleratorsFromWindowsStyle(
    613           l10n_util::GetStringUTF8(ids_id)).c_str());
    614   gtk_button_set_image(GTK_BUTTON(button),
    615                        gtk_image_new_from_stock(stock_id,
    616                                                 GTK_ICON_SIZE_BUTTON));
    617   return button;
    618 }
    619 
    620 GtkWidget* CreateEntryImageHBox(GtkWidget* entry, GtkWidget* image) {
    621   GtkWidget* hbox = gtk_hbox_new(FALSE, ui::kControlSpacing);
    622   gtk_box_pack_start(GTK_BOX(hbox), entry, TRUE, TRUE, 0);
    623   gtk_box_pack_start(GTK_BOX(hbox), image, FALSE, FALSE, 0);
    624   return hbox;
    625 }
    626 
    627 void SetLabelColor(GtkWidget* label, const GdkColor* color) {
    628   gtk_widget_modify_fg(label, GTK_STATE_NORMAL, color);
    629   gtk_widget_modify_fg(label, GTK_STATE_ACTIVE, color);
    630   gtk_widget_modify_fg(label, GTK_STATE_PRELIGHT, color);
    631   gtk_widget_modify_fg(label, GTK_STATE_INSENSITIVE, color);
    632 }
    633 
    634 GtkWidget* IndentWidget(GtkWidget* content) {
    635   GtkWidget* content_alignment = gtk_alignment_new(0.0, 0.5, 1.0, 1.0);
    636   gtk_alignment_set_padding(GTK_ALIGNMENT(content_alignment), 0, 0,
    637                             ui::kGroupIndent, 0);
    638   gtk_container_add(GTK_CONTAINER(content_alignment), content);
    639   return content_alignment;
    640 }
    641 
    642 GdkPoint MakeBidiGdkPoint(gint x, gint y, gint width, bool ltr) {
    643   GdkPoint point = {ltr ? x : width - x, y};
    644   return point;
    645 }
    646 
    647 std::string BuildTooltipTitleFor(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(ui::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(ui::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 
    675 void 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 
    711 void SetLayoutText(PangoLayout* layout, const 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 
    723 void 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 
    751 void 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 
    763 GdkColor AverageColors(GdkColor color_one, GdkColor color_two) {
    764   GdkColor average_color;
    765   average_color.pixel = 0;
    766   average_color.red = (color_one.red + color_two.red) / 2;
    767   average_color.green = (color_one.green + color_two.green) / 2;
    768   average_color.blue = (color_one.blue + color_two.blue) / 2;
    769   return average_color;
    770 }
    771 
    772 void SetAlwaysShowImage(GtkWidget* image_menu_item) {
    773   gtk_image_menu_item_set_always_show_image(
    774       GTK_IMAGE_MENU_ITEM(image_menu_item), TRUE);
    775 }
    776 
    777 gfx::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 
    795 void SuppressDefaultPainting(GtkWidget* container) {
    796   g_signal_connect(container, "expose-event",
    797                    G_CALLBACK(PaintNoBackground), NULL);
    798 }
    799 
    800 bool 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 
    827 gfx::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 
    841 void SetWMLastUserActionTime(GtkWindow* window) {
    842   gdk_x11_window_set_user_time(gtk_widget_get_window(GTK_WIDGET(window)),
    843                                XTimeNow());
    844 }
    845 
    846 guint32 XTimeNow() {
    847   struct timespec ts;
    848   clock_gettime(CLOCK_MONOTONIC, &ts);
    849   return ts.tv_sec * 1000 + ts.tv_nsec / 1000000;
    850 }
    851 
    852 bool 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 
    872 bool 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 
    881 void GetTextColors(GdkColor* normal_base,
    882                    GdkColor* selected_base,
    883                    GdkColor* normal_text,
    884                    GdkColor* selected_text) {
    885   GtkWidget* fake_entry = gtk_entry_new();
    886   GtkStyle* style = gtk_rc_get_style(fake_entry);
    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 
    901 void ShowDialog(GtkWidget* dialog) {
    902   gtk_widget_show_all(dialog);
    903 }
    904 
    905 void 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 
    917 void 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 
    930 void 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 
    937 gfx::Rect GetDialogBounds(GtkWidget* dialog) {
    938   gint x = 0, y = 0, width = 1, height = 1;
    939   gtk_window_get_position(GTK_WINDOW(dialog), &x, &y);
    940   gtk_window_get_size(GTK_WINDOW(dialog), &width, &height);
    941 
    942   return gfx::Rect(x, y, width, height);
    943 }
    944 
    945 string16 GetStockPreferencesMenuLabel() {
    946   GtkStockItem stock_item;
    947   string16 preferences;
    948   if (gtk_stock_lookup(GTK_STOCK_PREFERENCES, &stock_item)) {
    949     const char16 kUnderscore[] = { '_', 0 };
    950     RemoveChars(UTF8ToUTF16(stock_item.label), kUnderscore, &preferences);
    951   }
    952   return preferences;
    953 }
    954 
    955 bool 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 
    962 void 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 
    982 void 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 
    991 void 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