gtk2_ui.cc revision 5d1f7b1de12d16ceb2c938c56701a3e8bfa558f7
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/libgtk2ui/gtk2_ui.h"
6
7#include <set>
8
9#include "base/command_line.h"
10#include "base/environment.h"
11#include "base/i18n/rtl.h"
12#include "base/logging.h"
13#include "base/nix/mime_util_xdg.h"
14#include "base/stl_util.h"
15#include "base/strings/stringprintf.h"
16#include "chrome/browser/themes/theme_properties.h"
17#include "chrome/browser/ui/libgtk2ui/app_indicator_icon.h"
18#include "chrome/browser/ui/libgtk2ui/chrome_gtk_frame.h"
19#include "chrome/browser/ui/libgtk2ui/gtk2_border.h"
20#include "chrome/browser/ui/libgtk2ui/gtk2_util.h"
21#include "chrome/browser/ui/libgtk2ui/native_theme_gtk2.h"
22#include "chrome/browser/ui/libgtk2ui/print_dialog_gtk2.h"
23#include "chrome/browser/ui/libgtk2ui/printing_gtk2_util.h"
24#include "chrome/browser/ui/libgtk2ui/select_file_dialog_impl.h"
25#include "chrome/browser/ui/libgtk2ui/skia_utils_gtk2.h"
26#include "chrome/browser/ui/libgtk2ui/unity_service.h"
27#include "chrome/browser/ui/libgtk2ui/x11_input_method_context_impl_gtk2.h"
28#include "grit/theme_resources.h"
29#include "grit/ui_resources.h"
30#include "printing/printing_context_linux.h"
31#include "third_party/skia/include/core/SkBitmap.h"
32#include "third_party/skia/include/core/SkCanvas.h"
33#include "third_party/skia/include/core/SkColor.h"
34#include "third_party/skia/include/core/SkShader.h"
35#include "ui/base/resource/resource_bundle.h"
36#include "ui/gfx/canvas.h"
37#include "ui/gfx/image/image.h"
38#include "ui/gfx/rect.h"
39#include "ui/gfx/size.h"
40#include "ui/gfx/skbitmap_operations.h"
41#include "ui/gfx/skia_util.h"
42#include "ui/views/linux_ui/window_button_order_observer.h"
43
44#if defined(USE_GCONF)
45#include "chrome/browser/ui/libgtk2ui/gconf_titlebar_listener.h"
46#endif
47
48// A minimized port of GtkThemeService into something that can provide colors
49// and images for aura.
50//
51// TODO(erg): There's still a lot that needs ported or done for the first time:
52//
53// - Render and inject the button overlay from the gtk theme.
54// - Render and inject the omnibox background.
55// - Listen for the "style-set" signal on |fake_frame_| and recreate theme
56//   colors and images.
57// - Make sure to test with a light on dark theme, too.
58// - Everything else that we're not doing.
59
60namespace {
61
62struct GObjectDeleter {
63  void operator()(void* ptr) {
64    g_object_unref(ptr);
65  }
66};
67struct GtkIconInfoDeleter {
68  void operator()(GtkIconInfo* ptr) {
69    gtk_icon_info_free(ptr);
70  }
71};
72typedef scoped_ptr<GIcon, GObjectDeleter> ScopedGIcon;
73typedef scoped_ptr<GtkIconInfo, GtkIconInfoDeleter> ScopedGtkIconInfo;
74typedef scoped_ptr<GdkPixbuf, GObjectDeleter> ScopedGdkPixbuf;
75
76// Prefix for app indicator ids
77const char kAppIndicatorIdPrefix[] = "chrome_app_indicator_";
78
79// Number of app indicators used (used as part of app-indicator id).
80int indicators_count;
81
82// The size of the rendered toolbar image.
83const int kToolbarImageWidth = 64;
84const int kToolbarImageHeight = 128;
85
86// How much to tint the GTK+ color lighter at the top of the window.
87const color_utils::HSL kGtkFrameShift = { -1, -1, 0.58 };
88
89// How much to tint the GTK+ color when an explicit frame color hasn't been
90// specified.
91const color_utils::HSL kDefaultFrameShift = { -1, -1, 0.4 };
92
93// Values used as the new luminance and saturation values in the inactive tab
94// text color.
95const double kDarkInactiveLuminance = 0.85;
96const double kLightInactiveLuminance = 0.15;
97const double kHeavyInactiveSaturation = 0.7;
98const double kLightInactiveSaturation = 0.3;
99
100// Default color for links on the NTP when the GTK+ theme doesn't define a
101// link color. Constant taken from gtklinkbutton.c.
102const GdkColor kDefaultLinkColor = { 0, 0, 0, 0xeeee };
103
104const int kSkiaToGDKMultiplier = 257;
105
106// TODO(erg): ThemeService has a whole interface just for reading default
107// constants. Figure out what to do with that more long term; for now, just
108// copy the constants themselves here.
109//
110// Default tints.
111const color_utils::HSL kDefaultTintButtons = { -1, -1, -1 };
112const color_utils::HSL kDefaultTintFrame = { -1, -1, -1 };
113const color_utils::HSL kDefaultTintFrameInactive = { -1, -1, 0.75f };
114const color_utils::HSL kDefaultTintFrameIncognito = { -1, 0.2f, 0.35f };
115const color_utils::HSL kDefaultTintFrameIncognitoInactive = { -1, 0.3f, 0.6f };
116const color_utils::HSL kDefaultTintBackgroundTab = { -1, 0.5, 0.75 };
117
118// A list of images that we provide while in gtk mode.
119const int kThemeImages[] = {
120  IDR_THEME_TOOLBAR,
121  IDR_THEME_TAB_BACKGROUND,
122  IDR_THEME_TAB_BACKGROUND_INCOGNITO,
123  IDR_FRAME,
124  IDR_FRAME_INACTIVE,
125  IDR_THEME_FRAME,
126  IDR_THEME_FRAME_INACTIVE,
127  IDR_THEME_FRAME_INCOGNITO,
128  IDR_THEME_FRAME_INCOGNITO_INACTIVE,
129};
130
131// A list of icons used in the autocomplete view that should be tinted to the
132// current gtk theme selection color so they stand out against the GtkEntry's
133// base color.
134// TODO(erg): Decide what to do about other icons that appear in the omnibox,
135// e.g. content settings icons.
136const int kAutocompleteImages[] = {
137  IDR_OMNIBOX_EXTENSION_APP,
138  IDR_OMNIBOX_HTTP,
139  IDR_OMNIBOX_HTTP_DARK,
140  IDR_OMNIBOX_SEARCH,
141  IDR_OMNIBOX_SEARCH_DARK,
142  IDR_OMNIBOX_STAR,
143  IDR_OMNIBOX_STAR_DARK,
144  IDR_OMNIBOX_TTS,
145  IDR_OMNIBOX_TTS_DARK,
146};
147
148// This table converts button ids into a pair of gtk-stock id and state.
149struct IDRGtkMapping {
150  int idr;
151  const char* stock_id;
152  GtkStateType gtk_state;
153} const kGtkIcons[] = {
154  { IDR_BACK,      GTK_STOCK_GO_BACK,    GTK_STATE_NORMAL },
155  { IDR_BACK_D,    GTK_STOCK_GO_BACK,    GTK_STATE_INSENSITIVE },
156  { IDR_BACK_H,    GTK_STOCK_GO_BACK,    GTK_STATE_PRELIGHT },
157  { IDR_BACK_P,    GTK_STOCK_GO_BACK,    GTK_STATE_ACTIVE },
158
159  { IDR_FORWARD,   GTK_STOCK_GO_FORWARD, GTK_STATE_NORMAL },
160  { IDR_FORWARD_D, GTK_STOCK_GO_FORWARD, GTK_STATE_INSENSITIVE },
161  { IDR_FORWARD_H, GTK_STOCK_GO_FORWARD, GTK_STATE_PRELIGHT },
162  { IDR_FORWARD_P, GTK_STOCK_GO_FORWARD, GTK_STATE_ACTIVE },
163
164  { IDR_HOME,      GTK_STOCK_HOME,       GTK_STATE_NORMAL },
165  { IDR_HOME_H,    GTK_STOCK_HOME,       GTK_STATE_PRELIGHT },
166  { IDR_HOME_P,    GTK_STOCK_HOME,       GTK_STATE_ACTIVE },
167
168  { IDR_RELOAD,    GTK_STOCK_REFRESH,    GTK_STATE_NORMAL },
169  { IDR_RELOAD_D,  GTK_STOCK_REFRESH,    GTK_STATE_INSENSITIVE },
170  { IDR_RELOAD_H,  GTK_STOCK_REFRESH,    GTK_STATE_PRELIGHT },
171  { IDR_RELOAD_P,  GTK_STOCK_REFRESH,    GTK_STATE_ACTIVE },
172
173  { IDR_STOP,      GTK_STOCK_STOP,       GTK_STATE_NORMAL },
174  { IDR_STOP_D,    GTK_STOCK_STOP,       GTK_STATE_INSENSITIVE },
175  { IDR_STOP_H,    GTK_STOCK_STOP,       GTK_STATE_PRELIGHT },
176  { IDR_STOP_P,    GTK_STOCK_STOP,       GTK_STATE_ACTIVE },
177};
178
179// The image resources that will be tinted by the 'button' tint value.
180const int kOtherToolbarButtonIDs[] = {
181  IDR_TOOLBAR_BEZEL_HOVER,
182  IDR_TOOLBAR_BEZEL_PRESSED,
183  IDR_BROWSER_ACTION_H,
184  IDR_BROWSER_ACTION_P,
185  IDR_BROWSER_ACTIONS_OVERFLOW,
186  IDR_BROWSER_ACTIONS_OVERFLOW_H,
187  IDR_BROWSER_ACTIONS_OVERFLOW_P,
188  IDR_THROBBER,
189  IDR_THROBBER_WAITING,
190  IDR_THROBBER_LIGHT,
191
192  // TODO(erg): The dropdown arrow should be tinted because we're injecting
193  // various background GTK colors, but the code that accesses them needs to be
194  // modified so that they ask their ui::ThemeProvider instead of the
195  // ResourceBundle. (i.e. in a light on dark theme, the dropdown arrow will be
196  // dark on dark)
197  IDR_MENU_DROPARROW
198};
199
200bool IsOverridableImage(int id) {
201  CR_DEFINE_STATIC_LOCAL(std::set<int>, images, ());
202  if (images.empty()) {
203    images.insert(kThemeImages, kThemeImages + arraysize(kThemeImages));
204    images.insert(kAutocompleteImages,
205                  kAutocompleteImages + arraysize(kAutocompleteImages));
206
207    for (unsigned int i = 0; i < arraysize(kGtkIcons); ++i)
208      images.insert(kGtkIcons[i].idr);
209
210    images.insert(kOtherToolbarButtonIDs,
211                  kOtherToolbarButtonIDs + arraysize(kOtherToolbarButtonIDs));
212  }
213
214  return images.count(id) > 0;
215}
216
217// Picks a button tint from a set of background colors. While
218// |accent_gdk_color| will usually be the same color through a theme, this
219// function will get called with the normal GtkLabel |text_color|/GtkWindow
220// |background_color| pair and the GtkEntry |text_color|/|background_color|
221// pair. While 3/4 of the time the resulting tint will be the same, themes that
222// have a dark window background (with light text) and a light text entry (with
223// dark text) will get better icons with this separated out.
224void PickButtonTintFromColors(const GdkColor& accent_gdk_color,
225                              const GdkColor& text_color,
226                              const GdkColor& background_color,
227                              color_utils::HSL* tint) {
228  SkColor accent_color = libgtk2ui::GdkColorToSkColor(accent_gdk_color);
229  color_utils::HSL accent_tint;
230  color_utils::SkColorToHSL(accent_color, &accent_tint);
231
232  color_utils::HSL text_tint;
233  color_utils::SkColorToHSL(libgtk2ui::GdkColorToSkColor(text_color),
234                            &text_tint);
235
236  color_utils::HSL background_tint;
237  color_utils::SkColorToHSL(libgtk2ui::GdkColorToSkColor(background_color),
238                            &background_tint);
239
240  // If the accent color is gray, then our normal HSL tomfoolery will bring out
241  // whatever color is oddly dominant (for example, in rgb space [125, 128,
242  // 125] will tint green instead of gray). Slight differences (+/-10 (4%) to
243  // all color components) should be interpreted as this color being gray and
244  // we should switch into a special grayscale mode.
245  int rb_diff = abs(SkColorGetR(accent_color) - SkColorGetB(accent_color));
246  int rg_diff = abs(SkColorGetR(accent_color) - SkColorGetG(accent_color));
247  int bg_diff = abs(SkColorGetB(accent_color) - SkColorGetG(accent_color));
248  if (rb_diff < 10 && rg_diff < 10 && bg_diff < 10) {
249    // Our accent is white/gray/black. Only the luminance of the accent color
250    // matters.
251    tint->h = -1;
252
253    // Use the saturation of the text.
254    tint->s = text_tint.s;
255
256    // Use the luminance of the accent color UNLESS there isn't enough
257    // luminance contrast between the accent color and the base color.
258    if (fabs(accent_tint.l - background_tint.l) > 0.3)
259      tint->l = accent_tint.l;
260    else
261      tint->l = text_tint.l;
262  } else {
263    // Our accent is a color.
264    tint->h = accent_tint.h;
265
266    // Don't modify the saturation; the amount of color doesn't matter.
267    tint->s = -1;
268
269    // If the text wants us to darken the icon, don't change the luminance (the
270    // icons are already dark enough). Otherwise, lighten the icon by no more
271    // than 0.9 since we don't want a pure-white icon even if the text is pure
272    // white.
273    if (text_tint.l < 0.5)
274      tint->l = -1;
275    else if (text_tint.l <= 0.9)
276      tint->l = text_tint.l;
277    else
278      tint->l = 0.9;
279  }
280}
281
282// Applies an HSL shift to a GdkColor (instead of an SkColor)
283void GdkColorHSLShift(const color_utils::HSL& shift, GdkColor* frame_color) {
284  SkColor shifted = color_utils::HSLShift(
285      libgtk2ui::GdkColorToSkColor(*frame_color), shift);
286
287  frame_color->pixel = 0;
288  frame_color->red = SkColorGetR(shifted) * kSkiaToGDKMultiplier;
289  frame_color->green = SkColorGetG(shifted) * kSkiaToGDKMultiplier;
290  frame_color->blue = SkColorGetB(shifted) * kSkiaToGDKMultiplier;
291}
292
293// Copied Default blah sections from ThemeService.
294color_utils::HSL GetDefaultTint(int id) {
295  switch (id) {
296    case ThemeProperties::TINT_FRAME:
297      return kDefaultTintFrame;
298    case ThemeProperties::TINT_FRAME_INACTIVE:
299      return kDefaultTintFrameInactive;
300    case ThemeProperties::TINT_FRAME_INCOGNITO:
301      return kDefaultTintFrameIncognito;
302    case ThemeProperties::TINT_FRAME_INCOGNITO_INACTIVE:
303      return kDefaultTintFrameIncognitoInactive;
304    case ThemeProperties::TINT_BUTTONS:
305      return kDefaultTintButtons;
306    case ThemeProperties::TINT_BACKGROUND_TAB:
307      return kDefaultTintBackgroundTab;
308    default:
309      color_utils::HSL result = {-1, -1, -1};
310      return result;
311  }
312}
313
314}  // namespace
315
316namespace libgtk2ui {
317
318Gtk2UI::Gtk2UI() : use_gtk_(false) {
319  GtkInitFromCommandLine(*CommandLine::ForCurrentProcess());
320}
321
322void Gtk2UI::Initialize() {
323  // Create our fake widgets.
324  fake_window_ = gtk_window_new(GTK_WINDOW_TOPLEVEL);
325  fake_frame_ = chrome_gtk_frame_new();
326  fake_label_.Own(gtk_label_new(""));
327  fake_entry_.Own(gtk_entry_new());
328
329  // Only realized widgets receive style-set notifications, which we need to
330  // broadcast new theme images and colors. Only realized widgets have style
331  // properties, too, which we query for some colors.
332  gtk_widget_realize(fake_frame_);
333  gtk_widget_realize(fake_window_);
334  // TODO: Also listen for "style-set" on the fake frame.
335
336  // TODO(erg): Be lazy about generating this data and connect it to the
337  // style-set signal handler.
338  LoadGtkValues();
339  SetXDGIconTheme();
340
341  printing::PrintingContextLinux::SetCreatePrintDialogFunction(
342      &PrintDialogGtk2::CreatePrintDialog);
343  printing::PrintingContextLinux::SetPdfPaperSizeFunction(
344      &GetPdfPaperSizeDeviceUnitsGtk);
345
346#if defined(USE_GCONF)
347  // We must build this after GTK gets initialized.
348  titlebar_listener_.reset(new GConfTitlebarListener(this));
349#endif  // defined(USE_GCONF)
350
351  indicators_count = 0;
352}
353
354Gtk2UI::~Gtk2UI() {
355  gtk_widget_destroy(fake_window_);
356  gtk_widget_destroy(fake_frame_);
357  fake_label_.Destroy();
358  fake_entry_.Destroy();
359
360  ClearAllThemeData();
361}
362
363gfx::Image Gtk2UI::GetThemeImageNamed(int id) const {
364  // Try to get our cached version:
365  ImageCache::const_iterator it = gtk_images_.find(id);
366  if (it != gtk_images_.end())
367    return it->second;
368
369  if (/*use_gtk_ && */ IsOverridableImage(id)) {
370    gfx::Image image = gfx::Image(
371        gfx::ImageSkia::CreateFrom1xBitmap(GenerateGtkThemeBitmap(id)));
372    gtk_images_[id] = image;
373    return image;
374  }
375
376  return gfx::Image();
377}
378
379bool Gtk2UI::GetColor(int id, SkColor* color) const {
380  ColorMap::const_iterator it = colors_.find(id);
381  if (it != colors_.end()) {
382    *color = it->second;
383    return true;
384  }
385
386  return false;
387}
388
389bool Gtk2UI::HasCustomImage(int id) const {
390  return IsOverridableImage(id);
391}
392
393SkColor Gtk2UI::GetFocusRingColor() const {
394  return focus_ring_color_;
395}
396
397SkColor Gtk2UI::GetThumbActiveColor() const {
398  return thumb_active_color_;
399}
400
401SkColor Gtk2UI::GetThumbInactiveColor() const {
402  return thumb_inactive_color_;
403}
404
405SkColor Gtk2UI::GetTrackColor() const {
406  return track_color_;
407}
408
409SkColor Gtk2UI::GetActiveSelectionBgColor() const {
410  return active_selection_bg_color_;
411}
412
413SkColor Gtk2UI::GetActiveSelectionFgColor() const {
414  return active_selection_fg_color_;
415}
416
417SkColor Gtk2UI::GetInactiveSelectionBgColor() const {
418  return inactive_selection_bg_color_;
419}
420
421SkColor Gtk2UI::GetInactiveSelectionFgColor() const {
422  return inactive_selection_fg_color_;
423}
424
425double Gtk2UI::GetCursorBlinkInterval() const {
426  // From http://library.gnome.org/devel/gtk/unstable/GtkSettings.html, this is
427  // the default value for gtk-cursor-blink-time.
428  static const gint kGtkDefaultCursorBlinkTime = 1200;
429
430  // Dividing GTK's cursor blink cycle time (in milliseconds) by this value
431  // yields an appropriate value for
432  // content::RendererPreferences::caret_blink_interval.  This matches the
433  // logic in the WebKit GTK port.
434  static const double kGtkCursorBlinkCycleFactor = 2000.0;
435
436  gint cursor_blink_time = kGtkDefaultCursorBlinkTime;
437  gboolean cursor_blink = TRUE;
438  g_object_get(gtk_settings_get_default(),
439               "gtk-cursor-blink-time", &cursor_blink_time,
440               "gtk-cursor-blink", &cursor_blink,
441               NULL);
442  return cursor_blink ? (cursor_blink_time / kGtkCursorBlinkCycleFactor) : 0.0;
443}
444
445ui::NativeTheme* Gtk2UI::GetNativeTheme() const {
446  return use_gtk_ ? NativeThemeGtk2::instance() :
447                    ui::NativeTheme::instance();
448}
449
450void Gtk2UI::SetUseSystemTheme(bool use_system_theme) {
451  use_gtk_ = use_system_theme;
452
453  FOR_EACH_OBSERVER(Gtk2Border, border_list_,
454                    InvalidateAndSetUsesGtk(use_system_theme));
455}
456
457bool Gtk2UI::GetUseSystemTheme() const {
458  return use_gtk_;
459}
460
461bool Gtk2UI::GetDefaultUsesSystemTheme() const {
462  scoped_ptr<base::Environment> env(base::Environment::Create());
463
464  switch (base::nix::GetDesktopEnvironment(env.get())) {
465    case base::nix::DESKTOP_ENVIRONMENT_GNOME:
466    case base::nix::DESKTOP_ENVIRONMENT_UNITY:
467    case base::nix::DESKTOP_ENVIRONMENT_XFCE:
468      return true;
469    case base::nix::DESKTOP_ENVIRONMENT_KDE3:
470    case base::nix::DESKTOP_ENVIRONMENT_KDE4:
471    case base::nix::DESKTOP_ENVIRONMENT_OTHER:
472      return false;
473  }
474  // Unless GetDesktopEnvironment() badly misbehaves, this should never happen.
475  NOTREACHED();
476  return false;
477}
478
479void Gtk2UI::SetDownloadCount(int count) const {
480  if (unity::IsRunning())
481    unity::SetDownloadCount(count);
482}
483
484void Gtk2UI::SetProgressFraction(float percentage) const {
485  if (unity::IsRunning())
486    unity::SetProgressFraction(percentage);
487}
488
489bool Gtk2UI::IsStatusIconSupported() const {
490  return AppIndicatorIcon::CouldOpen();
491}
492
493scoped_ptr<views::StatusIconLinux> Gtk2UI::CreateLinuxStatusIcon(
494    const gfx::ImageSkia& image,
495    const base::string16& tool_tip) const {
496  if (AppIndicatorIcon::CouldOpen()) {
497    ++indicators_count;
498    return scoped_ptr<views::StatusIconLinux>(new AppIndicatorIcon(
499        base::StringPrintf("%s%d", kAppIndicatorIdPrefix, indicators_count),
500        image,
501        tool_tip));
502  } else {
503    return scoped_ptr<views::StatusIconLinux>();
504  }
505}
506
507gfx::Image Gtk2UI::GetIconForContentType(
508    const std::string& content_type,
509    int size) const {
510  // This call doesn't take a reference.
511  GtkIconTheme* theme = gtk_icon_theme_get_default();
512
513  ScopedGIcon icon(g_content_type_get_icon(content_type.c_str()));
514  ScopedGtkIconInfo icon_info(
515      gtk_icon_theme_lookup_by_gicon(
516          theme, icon.get(), size,
517          static_cast<GtkIconLookupFlags>(GTK_ICON_LOOKUP_FORCE_SIZE)));
518  if (!icon_info)
519    return gfx::Image();
520  ScopedGdkPixbuf pixbuf(gtk_icon_info_load_icon(icon_info.get(), NULL));
521  if (!pixbuf)
522    return gfx::Image();
523
524  SkBitmap bitmap = GdkPixbufToImageSkia(pixbuf.get());
525  DCHECK_EQ(size, bitmap.width());
526  DCHECK_EQ(size, bitmap.height());
527  gfx::ImageSkia image_skia = gfx::ImageSkia::CreateFrom1xBitmap(bitmap);
528  image_skia.MakeThreadSafe();
529  return gfx::Image(image_skia);
530}
531
532scoped_ptr<views::Border> Gtk2UI::CreateNativeBorder(
533    views::LabelButton* owning_button,
534    scoped_ptr<views::Border> border) {
535  return scoped_ptr<views::Border>(
536      new Gtk2Border(this, owning_button, border.Pass()));
537}
538
539void Gtk2UI::AddWindowButtonOrderObserver(
540    views::WindowButtonOrderObserver* observer) {
541  if (!leading_buttons_.empty() || !trailing_buttons_.empty()) {
542    observer->OnWindowButtonOrderingChange(leading_buttons_,
543                                           trailing_buttons_);
544  }
545
546  observer_list_.AddObserver(observer);
547}
548
549void Gtk2UI::RemoveWindowButtonOrderObserver(
550    views::WindowButtonOrderObserver* observer) {
551  observer_list_.RemoveObserver(observer);
552}
553
554void Gtk2UI::SetWindowButtonOrdering(
555    const std::vector<views::FrameButton>& leading_buttons,
556    const std::vector<views::FrameButton>& trailing_buttons) {
557  leading_buttons_ = leading_buttons;
558  trailing_buttons_ = trailing_buttons;
559
560  FOR_EACH_OBSERVER(views::WindowButtonOrderObserver, observer_list_,
561                    OnWindowButtonOrderingChange(leading_buttons_,
562                                                 trailing_buttons_));
563}
564
565scoped_ptr<ui::LinuxInputMethodContext> Gtk2UI::CreateInputMethodContext(
566    ui::LinuxInputMethodContextDelegate* delegate) const {
567  return scoped_ptr<ui::LinuxInputMethodContext>(
568      new X11InputMethodContextImplGtk2(delegate));
569}
570
571bool Gtk2UI::UseAntialiasing() const {
572  GtkSettings* gtk_settings = gtk_settings_get_default();
573  CHECK(gtk_settings);
574  gint gtk_antialias = 0;
575  g_object_get(gtk_settings,
576               "gtk-xft-antialias", &gtk_antialias,
577               NULL);
578  return gtk_antialias != 0;
579}
580
581gfx::FontRenderParams::Hinting Gtk2UI::GetHintingStyle() const {
582  GtkSettings* gtk_settings = gtk_settings_get_default();
583  CHECK(gtk_settings);
584  gfx::FontRenderParams::Hinting hinting =
585      gfx::FontRenderParams::HINTING_SLIGHT;
586  gint gtk_hinting = 0;
587  gchar* gtk_hint_style = NULL;
588  g_object_get(gtk_settings,
589               "gtk-xft-hinting", &gtk_hinting,
590               "gtk-xft-hintstyle", &gtk_hint_style,
591               NULL);
592
593  if (gtk_hint_style) {
594    if (gtk_hinting == 0 || strcmp(gtk_hint_style, "hintnone") == 0)
595      hinting = gfx::FontRenderParams::HINTING_NONE;
596    else if (strcmp(gtk_hint_style, "hintslight") == 0)
597      hinting = gfx::FontRenderParams::HINTING_SLIGHT;
598    else if (strcmp(gtk_hint_style, "hintmedium") == 0)
599      hinting = gfx::FontRenderParams::HINTING_MEDIUM;
600    else if (strcmp(gtk_hint_style, "hintfull") == 0)
601      hinting = gfx::FontRenderParams::HINTING_FULL;
602
603    g_free(gtk_hint_style);
604  }
605
606  return hinting;
607}
608
609gfx::FontRenderParams::SubpixelRendering
610Gtk2UI::GetSubpixelRenderingStyle() const {
611  GtkSettings* gtk_settings = gtk_settings_get_default();
612  CHECK(gtk_settings);
613  gfx::FontRenderParams::SubpixelRendering subpixel_rendering =
614      gfx::FontRenderParams::SUBPIXEL_RENDERING_NONE;
615  gchar* gtk_rgba = NULL;
616  g_object_get(gtk_settings,
617               "gtk-xft-rgba", &gtk_rgba,
618               NULL);
619
620  if (gtk_rgba) {
621    if (strcmp(gtk_rgba, "none") == 0)
622      subpixel_rendering = gfx::FontRenderParams::SUBPIXEL_RENDERING_NONE;
623    else if (strcmp(gtk_rgba, "rgb") == 0)
624      subpixel_rendering = gfx::FontRenderParams::SUBPIXEL_RENDERING_RGB;
625    else if (strcmp(gtk_rgba, "bgr") == 0)
626      subpixel_rendering = gfx::FontRenderParams::SUBPIXEL_RENDERING_BGR;
627    else if (strcmp(gtk_rgba, "vrgb") == 0)
628      subpixel_rendering = gfx::FontRenderParams::SUBPIXEL_RENDERING_VRGB;
629    else if (strcmp(gtk_rgba, "vbgr") == 0)
630      subpixel_rendering = gfx::FontRenderParams::SUBPIXEL_RENDERING_VBGR;
631
632    g_free(gtk_rgba);
633  }
634
635  return subpixel_rendering;
636}
637
638std::string Gtk2UI::GetDefaultFontName() const {
639  GtkSettings* gtk_settings = gtk_settings_get_default();
640  CHECK(gtk_settings);
641
642  std::string out_font_name = "sans 10";
643  gchar* font_name = NULL;
644  g_object_get(gtk_settings, "gtk-font-name", &font_name, NULL);
645
646  if (font_name) {
647    out_font_name = std::string(font_name);
648    g_free(font_name);
649  }
650
651  return out_font_name;
652}
653
654ui::SelectFileDialog* Gtk2UI::CreateSelectFileDialog(
655    ui::SelectFileDialog::Listener* listener,
656    ui::SelectFilePolicy* policy) const {
657  return SelectFileDialogImpl::Create(listener, policy);
658}
659
660bool Gtk2UI::UnityIsRunning() {
661  return unity::IsRunning();
662}
663
664void Gtk2UI::NotifyWindowManagerStartupComplete() {
665  // TODO(port) Implement this using _NET_STARTUP_INFO_BEGIN/_NET_STARTUP_INFO
666  // from http://standards.freedesktop.org/startup-notification-spec/ instead.
667  gdk_notify_startup_complete();
668}
669
670void Gtk2UI::GetScrollbarColors(GdkColor* thumb_active_color,
671                                GdkColor* thumb_inactive_color,
672                                GdkColor* track_color) {
673  const GdkColor* theme_thumb_active = NULL;
674  const GdkColor* theme_thumb_inactive = NULL;
675  const GdkColor* theme_trough_color = NULL;
676  gtk_widget_style_get(GTK_WIDGET(fake_frame_),
677                       "scrollbar-slider-prelight-color", &theme_thumb_active,
678                       "scrollbar-slider-normal-color", &theme_thumb_inactive,
679                       "scrollbar-trough-color", &theme_trough_color,
680                       NULL);
681
682  // Ask the theme if the theme specifies all the scrollbar colors and short
683  // circuit the expensive painting/compositing if we have all of them.
684  if (theme_thumb_active && theme_thumb_inactive && theme_trough_color) {
685    *thumb_active_color = *theme_thumb_active;
686    *thumb_inactive_color = *theme_thumb_inactive;
687    *track_color = *theme_trough_color;
688    return;
689  }
690
691  // Create window containing scrollbar elements
692  GtkWidget* window    = gtk_window_new(GTK_WINDOW_POPUP);
693  GtkWidget* fixed     = gtk_fixed_new();
694  GtkWidget* scrollbar = gtk_hscrollbar_new(NULL);
695  gtk_container_add(GTK_CONTAINER(window), fixed);
696  gtk_container_add(GTK_CONTAINER(fixed),  scrollbar);
697  gtk_widget_realize(window);
698  gtk_widget_realize(scrollbar);
699
700  // Draw scrollbar thumb part and track into offscreen image
701  const int kWidth  = 100;
702  const int kHeight = 20;
703  GtkStyle*  style  = gtk_rc_get_style(scrollbar);
704  GdkWindow* gdk_window = gtk_widget_get_window(window);
705  GdkPixmap* pm     = gdk_pixmap_new(gdk_window, kWidth, kHeight, -1);
706  GdkRectangle rect = { 0, 0, kWidth, kHeight };
707  unsigned char data[3 * kWidth * kHeight];
708  for (int i = 0; i < 3; ++i) {
709    if (i < 2) {
710      // Thumb part
711      gtk_paint_slider(style, pm,
712                       i == 0 ? GTK_STATE_PRELIGHT : GTK_STATE_NORMAL,
713                       GTK_SHADOW_OUT, &rect, scrollbar, "slider", 0, 0,
714                       kWidth, kHeight, GTK_ORIENTATION_HORIZONTAL);
715    } else {
716      // Track
717      gtk_paint_box(style, pm, GTK_STATE_ACTIVE, GTK_SHADOW_IN, &rect,
718                    scrollbar, "trough-upper", 0, 0, kWidth, kHeight);
719    }
720    GdkPixbuf* pb = gdk_pixbuf_new_from_data(data, GDK_COLORSPACE_RGB,
721                                             FALSE, 8, kWidth, kHeight,
722                                             3 * kWidth, 0, 0);
723    gdk_pixbuf_get_from_drawable(pb, pm, NULL, 0, 0, 0, 0, kWidth, kHeight);
724
725    // Sample pixels
726    int components[3] = { 0 };
727    for (int y = 2; y < kHeight - 2; ++y) {
728      for (int c = 0; c < 3; ++c) {
729        // Sample a vertical slice of pixels at about one-thirds from the
730        // left edge. This allows us to avoid any fixed graphics that might be
731        // located at the edges or in the center of the scrollbar.
732        // Each pixel is made up of a red, green, and blue component; taking up
733        // a total of three bytes.
734        components[c] += data[3 * (kWidth / 3 + y * kWidth) + c];
735      }
736    }
737    GdkColor* color = i == 0 ? thumb_active_color :
738                      i == 1 ? thumb_inactive_color :
739                               track_color;
740    color->pixel = 0;
741    // We sampled pixels across the full height of the image, ignoring a two
742    // pixel border. In some themes, the border has a completely different
743    // color which we do not want to factor into our average color computation.
744    //
745    // We now need to scale the colors from the 0..255 range, to the wider
746    // 0..65535 range, and we need to actually compute the average color; so,
747    // we divide by the total number of pixels in the sample.
748    color->red   = components[0] * 65535 / (255 * (kHeight - 4));
749    color->green = components[1] * 65535 / (255 * (kHeight - 4));
750    color->blue  = components[2] * 65535 / (255 * (kHeight - 4));
751
752    g_object_unref(pb);
753  }
754  g_object_unref(pm);
755
756  gtk_widget_destroy(window);
757
758  // Override any of the default colors with ones that were specified by the
759  // theme.
760  if (theme_thumb_active)
761    *thumb_active_color = *theme_thumb_active;
762
763  if (theme_thumb_inactive)
764    *thumb_inactive_color = *theme_thumb_inactive;
765
766  if (theme_trough_color)
767    *track_color = *theme_trough_color;
768}
769
770void Gtk2UI::SetXDGIconTheme() {
771  gchar* gtk_theme_name;
772  g_object_get(gtk_settings_get_default(),
773               "gtk-icon-theme-name",
774               &gtk_theme_name, NULL);
775  base::nix::SetIconThemeName(gtk_theme_name);
776  g_free(gtk_theme_name);
777}
778
779void Gtk2UI::LoadGtkValues() {
780  // TODO(erg): GtkThemeService had a comment here about having to muck with
781  // the raw Prefs object to remove prefs::kCurrentThemeImages or else we'd
782  // regress startup time. Figure out how to do that when we can't access the
783  // prefs system from here.
784
785  GtkStyle* frame_style = gtk_rc_get_style(fake_frame_);
786
787  GtkStyle* window_style = gtk_rc_get_style(fake_window_);
788  SetThemeColorFromGtk(ThemeProperties::COLOR_CONTROL_BACKGROUND,
789                       &window_style->bg[GTK_STATE_NORMAL]);
790
791  GdkColor toolbar_color = window_style->bg[GTK_STATE_NORMAL];
792  SetThemeColorFromGtk(ThemeProperties::COLOR_TOOLBAR, &toolbar_color);
793
794  GdkColor button_color = window_style->bg[GTK_STATE_SELECTED];
795  SetThemeTintFromGtk(ThemeProperties::TINT_BUTTONS, &button_color);
796
797  GtkStyle* label_style = gtk_rc_get_style(fake_label_.get());
798  GdkColor label_color = label_style->fg[GTK_STATE_NORMAL];
799  SetThemeColorFromGtk(ThemeProperties::COLOR_TAB_TEXT, &label_color);
800  SetThemeColorFromGtk(ThemeProperties::COLOR_BOOKMARK_TEXT, &label_color);
801  SetThemeColorFromGtk(ThemeProperties::COLOR_STATUS_BAR_TEXT, &label_color);
802
803  // Build the various icon tints.
804  GetNormalButtonTintHSL(&button_tint_);
805  GetNormalEntryForegroundHSL(&entry_tint_);
806  GetSelectedEntryForegroundHSL(&selected_entry_tint_);
807  GdkColor frame_color = BuildFrameColors(frame_style);
808
809  // The inactive frame color never occurs naturally in the theme, as it is a
810  // tinted version of |frame_color|. We generate another color based on the
811  // background tab color, with the lightness and saturation moved in the
812  // opposite direction. (We don't touch the hue, since there should be subtle
813  // hints of the color in the text.)
814  color_utils::HSL inactive_tab_text_hsl =
815      tints_[ThemeProperties::TINT_BACKGROUND_TAB];
816  if (inactive_tab_text_hsl.l < 0.5)
817    inactive_tab_text_hsl.l = kDarkInactiveLuminance;
818  else
819    inactive_tab_text_hsl.l = kLightInactiveLuminance;
820
821  if (inactive_tab_text_hsl.s < 0.5)
822    inactive_tab_text_hsl.s = kHeavyInactiveSaturation;
823  else
824    inactive_tab_text_hsl.s = kLightInactiveSaturation;
825
826  colors_[ThemeProperties::COLOR_BACKGROUND_TAB_TEXT] =
827      color_utils::HSLToSkColor(inactive_tab_text_hsl, 255);
828
829  // We pick the text and background colors for the NTP out of the colors for a
830  // GtkEntry. We do this because GtkEntries background color is never the same
831  // as |toolbar_color|, is usually a white, and when it isn't a white,
832  // provides sufficient contrast to |toolbar_color|. Try this out with
833  // Darklooks, HighContrastInverse or ThinIce.
834  GtkStyle* entry_style = gtk_rc_get_style(fake_entry_.get());
835  GdkColor ntp_background = entry_style->base[GTK_STATE_NORMAL];
836  GdkColor ntp_foreground = entry_style->text[GTK_STATE_NORMAL];
837  SetThemeColorFromGtk(ThemeProperties::COLOR_NTP_BACKGROUND,
838                       &ntp_background);
839  SetThemeColorFromGtk(ThemeProperties::COLOR_NTP_TEXT,
840                       &ntp_foreground);
841
842  // The NTP header is the color that surrounds the current active thumbnail on
843  // the NTP, and acts as the border of the "Recent Links" box. It would be
844  // awesome if they were separated so we could use GetBorderColor() for the
845  // border around the "Recent Links" section, but matching the frame color is
846  // more important.
847  SetThemeColorFromGtk(ThemeProperties::COLOR_NTP_HEADER,
848                       &frame_color);
849  SetThemeColorFromGtk(ThemeProperties::COLOR_NTP_SECTION,
850                       &toolbar_color);
851  SetThemeColorFromGtk(ThemeProperties::COLOR_NTP_SECTION_TEXT,
852                       &label_color);
853
854  // Override the link color if the theme provides it.
855  const GdkColor* link_color = NULL;
856  gtk_widget_style_get(GTK_WIDGET(fake_window_),
857                       "link-color", &link_color, NULL);
858
859  bool is_default_link_color = false;
860  if (!link_color) {
861    link_color = &kDefaultLinkColor;
862    is_default_link_color = true;
863  }
864
865  SetThemeColorFromGtk(ThemeProperties::COLOR_NTP_LINK,
866                       link_color);
867  SetThemeColorFromGtk(ThemeProperties::COLOR_NTP_LINK_UNDERLINE,
868                       link_color);
869  SetThemeColorFromGtk(ThemeProperties::COLOR_NTP_SECTION_LINK,
870                       link_color);
871  SetThemeColorFromGtk(ThemeProperties::COLOR_NTP_SECTION_LINK_UNDERLINE,
872                       link_color);
873
874  if (!is_default_link_color)
875    gdk_color_free(const_cast<GdkColor*>(link_color));
876
877  // Generate the colors that we pass to WebKit.
878  focus_ring_color_ = GdkColorToSkColor(frame_color);
879
880  GdkColor thumb_active_color, thumb_inactive_color, track_color;
881  Gtk2UI::GetScrollbarColors(&thumb_active_color,
882                             &thumb_inactive_color,
883                             &track_color);
884  thumb_active_color_ = GdkColorToSkColor(thumb_active_color);
885  thumb_inactive_color_ = GdkColorToSkColor(thumb_inactive_color);
886  track_color_ = GdkColorToSkColor(track_color);
887
888  // Some GTK themes only define the text selection colors on the GtkEntry
889  // class, so we need to use that for getting selection colors.
890  active_selection_bg_color_ =
891      GdkColorToSkColor(entry_style->base[GTK_STATE_SELECTED]);
892  active_selection_fg_color_ =
893      GdkColorToSkColor(entry_style->text[GTK_STATE_SELECTED]);
894  inactive_selection_bg_color_ =
895      GdkColorToSkColor(entry_style->base[GTK_STATE_ACTIVE]);
896  inactive_selection_fg_color_ =
897      GdkColorToSkColor(entry_style->text[GTK_STATE_ACTIVE]);
898
899  // Update the insets that we hand to Gtk2Border.
900  UpdateButtonInsets();
901}
902
903GdkColor Gtk2UI::BuildFrameColors(GtkStyle* frame_style) {
904  GdkColor* theme_frame = NULL;
905  GdkColor* theme_inactive_frame = NULL;
906  GdkColor* theme_incognito_frame = NULL;
907  GdkColor* theme_incognito_inactive_frame = NULL;
908  gtk_widget_style_get(GTK_WIDGET(fake_frame_),
909                       "frame-color", &theme_frame,
910                       "inactive-frame-color", &theme_inactive_frame,
911                       "incognito-frame-color", &theme_incognito_frame,
912                       "incognito-inactive-frame-color",
913                       &theme_incognito_inactive_frame,
914                       NULL);
915
916  GdkColor frame_color = BuildAndSetFrameColor(
917      &frame_style->bg[GTK_STATE_SELECTED],
918      theme_frame,
919      kDefaultFrameShift,
920      ThemeProperties::COLOR_FRAME,
921      ThemeProperties::TINT_FRAME);
922  if (theme_frame)
923    gdk_color_free(theme_frame);
924  SetThemeTintFromGtk(ThemeProperties::TINT_BACKGROUND_TAB, &frame_color);
925
926  BuildAndSetFrameColor(
927      &frame_style->bg[GTK_STATE_INSENSITIVE],
928      theme_inactive_frame,
929      kDefaultFrameShift,
930      ThemeProperties::COLOR_FRAME_INACTIVE,
931      ThemeProperties::TINT_FRAME_INACTIVE);
932  if (theme_inactive_frame)
933    gdk_color_free(theme_inactive_frame);
934
935  BuildAndSetFrameColor(
936      &frame_color,
937      theme_incognito_frame,
938      GetDefaultTint(ThemeProperties::TINT_FRAME_INCOGNITO),
939      ThemeProperties::COLOR_FRAME_INCOGNITO,
940      ThemeProperties::TINT_FRAME_INCOGNITO);
941  if (theme_incognito_frame)
942    gdk_color_free(theme_incognito_frame);
943
944  BuildAndSetFrameColor(
945      &frame_color,
946      theme_incognito_inactive_frame,
947      GetDefaultTint(ThemeProperties::TINT_FRAME_INCOGNITO_INACTIVE),
948      ThemeProperties::COLOR_FRAME_INCOGNITO_INACTIVE,
949      ThemeProperties::TINT_FRAME_INCOGNITO_INACTIVE);
950  if (theme_incognito_inactive_frame)
951    gdk_color_free(theme_incognito_inactive_frame);
952
953  return frame_color;
954}
955
956void Gtk2UI::SetThemeColorFromGtk(int id, const GdkColor* color) {
957  colors_[id] = GdkColorToSkColor(*color);
958}
959
960void Gtk2UI::SetThemeTintFromGtk(int id, const GdkColor* color) {
961  color_utils::HSL default_tint = GetDefaultTint(id);
962  color_utils::HSL hsl;
963  color_utils::SkColorToHSL(GdkColorToSkColor(*color), &hsl);
964
965  if (default_tint.s != -1)
966    hsl.s = default_tint.s;
967
968  if (default_tint.l != -1)
969    hsl.l = default_tint.l;
970
971  tints_[id] = hsl;
972}
973
974GdkColor Gtk2UI::BuildAndSetFrameColor(const GdkColor* base,
975                                       const GdkColor* gtk_base,
976                                       const color_utils::HSL& tint,
977                                       int color_id,
978                                       int tint_id) {
979  GdkColor out_color = *base;
980  if (gtk_base) {
981    // The theme author specified a color to use, use it without modification.
982    out_color = *gtk_base;
983  } else {
984    // Tint the basic color since this is a heuristic color instead of one
985    // specified by the theme author.
986    GdkColorHSLShift(tint, &out_color);
987  }
988  SetThemeColorFromGtk(color_id, &out_color);
989  SetThemeTintFromGtk(tint_id, &out_color);
990
991  return out_color;
992}
993
994SkBitmap Gtk2UI::GenerateGtkThemeBitmap(int id) const {
995  switch (id) {
996    case IDR_THEME_TOOLBAR: {
997      GtkStyle* style = gtk_rc_get_style(fake_window_);
998      GdkColor* color = &style->bg[GTK_STATE_NORMAL];
999      SkBitmap bitmap;
1000      bitmap.setConfig(SkBitmap::kARGB_8888_Config,
1001                       kToolbarImageWidth, kToolbarImageHeight);
1002      bitmap.allocPixels();
1003      bitmap.eraseRGB(color->red >> 8, color->green >> 8, color->blue >> 8);
1004      return bitmap;
1005    }
1006    case IDR_THEME_TAB_BACKGROUND:
1007      return GenerateTabImage(IDR_THEME_FRAME);
1008    case IDR_THEME_TAB_BACKGROUND_INCOGNITO:
1009      return GenerateTabImage(IDR_THEME_FRAME_INCOGNITO);
1010    case IDR_FRAME:
1011    case IDR_THEME_FRAME:
1012      return GenerateFrameImage(ThemeProperties::COLOR_FRAME,
1013                                "frame-gradient-color");
1014    case IDR_FRAME_INACTIVE:
1015    case IDR_THEME_FRAME_INACTIVE:
1016      return GenerateFrameImage(ThemeProperties::COLOR_FRAME_INACTIVE,
1017                                "inactive-frame-gradient-color");
1018    case IDR_THEME_FRAME_INCOGNITO:
1019      return GenerateFrameImage(ThemeProperties::COLOR_FRAME_INCOGNITO,
1020                                "incognito-frame-gradient-color");
1021    case IDR_THEME_FRAME_INCOGNITO_INACTIVE: {
1022      return GenerateFrameImage(
1023          ThemeProperties::COLOR_FRAME_INCOGNITO_INACTIVE,
1024          "incognito-inactive-frame-gradient-color");
1025    }
1026    // Icons that sit inside the omnibox shouldn't receive TINT_BUTTONS and
1027    // instead should tint based on the foreground text entry color in GTK+
1028    // mode because some themes that try to be dark *and* light have very
1029    // different colors between the omnibox and the normal background area.
1030    // TODO(erg): Decide what to do about other icons that appear in the
1031    // omnibox, e.g. content settings icons.
1032    case IDR_OMNIBOX_EXTENSION_APP:
1033    case IDR_OMNIBOX_HTTP:
1034    case IDR_OMNIBOX_SEARCH:
1035    case IDR_OMNIBOX_STAR:
1036    case IDR_OMNIBOX_TTS: {
1037      return GenerateTintedIcon(id, entry_tint_);
1038    }
1039    // In GTK mode, the dark versions of the omnibox icons only ever appear in
1040    // the autocomplete popup and only against the current theme's GtkEntry
1041    // base[GTK_STATE_SELECTED] color, so tint the icons so they won't collide
1042    // with the selected color.
1043    case IDR_OMNIBOX_EXTENSION_APP_DARK:
1044    case IDR_OMNIBOX_HTTP_DARK:
1045    case IDR_OMNIBOX_SEARCH_DARK:
1046    case IDR_OMNIBOX_STAR_DARK:
1047    case IDR_OMNIBOX_TTS_DARK: {
1048      return GenerateTintedIcon(id, selected_entry_tint_);
1049    }
1050    // In GTK mode, we need to manually render several icons.
1051    case IDR_BACK:
1052    case IDR_BACK_D:
1053    case IDR_BACK_H:
1054    case IDR_BACK_P:
1055    case IDR_FORWARD:
1056    case IDR_FORWARD_D:
1057    case IDR_FORWARD_H:
1058    case IDR_FORWARD_P:
1059    case IDR_HOME:
1060    case IDR_HOME_H:
1061    case IDR_HOME_P:
1062    case IDR_RELOAD:
1063    case IDR_RELOAD_D:
1064    case IDR_RELOAD_H:
1065    case IDR_RELOAD_P:
1066    case IDR_STOP:
1067    case IDR_STOP_D:
1068    case IDR_STOP_H:
1069    case IDR_STOP_P: {
1070      return GenerateGTKIcon(id);
1071    }
1072    case IDR_TOOLBAR_BEZEL_HOVER:
1073      return GenerateToolbarBezel(GTK_STATE_PRELIGHT, IDR_TOOLBAR_BEZEL_HOVER);
1074    case IDR_TOOLBAR_BEZEL_PRESSED:
1075      return GenerateToolbarBezel(GTK_STATE_ACTIVE, IDR_TOOLBAR_BEZEL_PRESSED);
1076    case IDR_BROWSER_ACTION_H:
1077      return GenerateToolbarBezel(GTK_STATE_PRELIGHT, IDR_BROWSER_ACTION_H);
1078    case IDR_BROWSER_ACTION_P:
1079      return GenerateToolbarBezel(GTK_STATE_ACTIVE, IDR_BROWSER_ACTION_P);
1080    default: {
1081      return GenerateTintedIcon(id, button_tint_);
1082    }
1083  }
1084
1085  return SkBitmap();
1086}
1087
1088SkBitmap Gtk2UI::GenerateFrameImage(
1089    int color_id,
1090    const char* gradient_name) const {
1091  // We use two colors: the main color (passed in) and a lightened version of
1092  // that color (which is supposed to match the light gradient at the top of
1093  // several GTK+ themes, such as Ambiance, Clearlooks or Bluebird).
1094  ColorMap::const_iterator it = colors_.find(color_id);
1095  DCHECK(it != colors_.end());
1096  SkColor base = it->second;
1097
1098  gfx::Canvas canvas(gfx::Size(kToolbarImageWidth, kToolbarImageHeight),
1099      1.0f, true);
1100
1101  int gradient_size;
1102  GdkColor* gradient_top_color = NULL;
1103  gtk_widget_style_get(GTK_WIDGET(fake_frame_),
1104                       "frame-gradient-size", &gradient_size,
1105                       gradient_name, &gradient_top_color,
1106                       NULL);
1107  if (gradient_size) {
1108    SkColor lighter = gradient_top_color ?
1109        GdkColorToSkColor(*gradient_top_color) :
1110        color_utils::HSLShift(base, kGtkFrameShift);
1111    if (gradient_top_color)
1112      gdk_color_free(gradient_top_color);
1113    skia::RefPtr<SkShader> shader = gfx::CreateGradientShader(
1114        0, gradient_size, lighter, base);
1115    SkPaint paint;
1116    paint.setStyle(SkPaint::kFill_Style);
1117    paint.setAntiAlias(true);
1118    paint.setShader(shader.get());
1119
1120    canvas.DrawRect(gfx::Rect(0, 0, kToolbarImageWidth, gradient_size), paint);
1121  }
1122
1123  canvas.FillRect(gfx::Rect(0, gradient_size, kToolbarImageWidth,
1124                            kToolbarImageHeight - gradient_size), base);
1125  return canvas.ExtractImageRep().sk_bitmap();
1126}
1127
1128SkBitmap Gtk2UI::GenerateTabImage(int base_id) const {
1129  const SkBitmap* base_image = GetThemeImageNamed(base_id).ToSkBitmap();
1130  SkBitmap bg_tint = SkBitmapOperations::CreateHSLShiftedBitmap(
1131      *base_image, GetDefaultTint(ThemeProperties::TINT_BACKGROUND_TAB));
1132  return SkBitmapOperations::CreateTiledBitmap(
1133      bg_tint, 0, 0, bg_tint.width(), bg_tint.height());
1134}
1135
1136SkBitmap Gtk2UI::GenerateTintedIcon(
1137    int base_id,
1138    const color_utils::HSL& tint) const {
1139  ui::ResourceBundle& rb = ui::ResourceBundle::GetSharedInstance();
1140  return SkBitmapOperations::CreateHSLShiftedBitmap(
1141      rb.GetImageNamed(base_id).AsBitmap(), tint);
1142}
1143
1144SkBitmap Gtk2UI::GenerateGTKIcon(int base_id) const {
1145  const char* stock_id = NULL;
1146  GtkStateType gtk_state = GTK_STATE_NORMAL;
1147  for (unsigned int i = 0; i < arraysize(kGtkIcons); ++i) {
1148    if (kGtkIcons[i].idr == base_id) {
1149      stock_id = kGtkIcons[i].stock_id;
1150      gtk_state = kGtkIcons[i].gtk_state;
1151      break;
1152    }
1153  }
1154  DCHECK(stock_id);
1155
1156  ui::ResourceBundle& rb = ui::ResourceBundle::GetSharedInstance();
1157  SkBitmap default_bitmap = rb.GetImageNamed(base_id).AsBitmap();
1158
1159  gtk_widget_ensure_style(fake_frame_);
1160  GtkStyle* style = gtk_widget_get_style(fake_frame_);
1161  GtkIconSet* icon_set = gtk_style_lookup_icon_set(style, stock_id);
1162  if (!icon_set)
1163    return default_bitmap;
1164
1165  // Ask GTK to render the icon to a buffer, which we will steal from.
1166  GdkPixbuf* gdk_icon = gtk_icon_set_render_icon(
1167      icon_set,
1168      style,
1169      base::i18n::IsRTL() ? GTK_TEXT_DIR_RTL : GTK_TEXT_DIR_LTR,
1170      gtk_state,
1171      GTK_ICON_SIZE_SMALL_TOOLBAR,
1172      fake_frame_,
1173      NULL);
1174
1175  if (!gdk_icon) {
1176    // This can theoretically happen if an icon theme doesn't provide a
1177    // specific image. This should realistically never happen, but I bet there
1178    // are some theme authors who don't reliably provide all icons.
1179    return default_bitmap;
1180  }
1181
1182  SkBitmap retval;
1183  retval.setConfig(SkBitmap::kARGB_8888_Config,
1184                   default_bitmap.width(),
1185                   default_bitmap.height());
1186  retval.allocPixels();
1187  retval.eraseColor(0);
1188
1189  const SkBitmap icon = GdkPixbufToImageSkia(gdk_icon);
1190  g_object_unref(gdk_icon);
1191
1192  SkCanvas canvas(retval);
1193
1194  if (gtk_state == GTK_STATE_ACTIVE || gtk_state == GTK_STATE_PRELIGHT) {
1195    SkBitmap border = DrawGtkButtonBorder(gtk_state,
1196                                          false,
1197                                          default_bitmap.width(),
1198                                          default_bitmap.height());
1199    canvas.drawBitmap(border, 0, 0);
1200  }
1201
1202  canvas.drawBitmap(icon,
1203                    (default_bitmap.width() / 2) - (icon.width() / 2),
1204                    (default_bitmap.height() / 2) - (icon.height() / 2));
1205
1206  return retval;
1207}
1208
1209SkBitmap Gtk2UI::GenerateToolbarBezel(int gtk_state, int sizing_idr) const {
1210  ui::ResourceBundle& rb = ui::ResourceBundle::GetSharedInstance();
1211  SkBitmap default_bitmap =
1212      rb.GetImageNamed(sizing_idr).AsBitmap();
1213
1214  SkBitmap retval;
1215  retval.setConfig(SkBitmap::kARGB_8888_Config,
1216                   default_bitmap.width(),
1217                   default_bitmap.height());
1218  retval.allocPixels();
1219  retval.eraseColor(0);
1220
1221  SkCanvas canvas(retval);
1222  SkBitmap border = DrawGtkButtonBorder(
1223      gtk_state,
1224      false,
1225      default_bitmap.width(),
1226      default_bitmap.height());
1227  canvas.drawBitmap(border, 0, 0);
1228
1229  return retval;
1230}
1231
1232void Gtk2UI::GetNormalButtonTintHSL(color_utils::HSL* tint) const {
1233  GtkStyle* window_style = gtk_rc_get_style(fake_window_);
1234  const GdkColor accent_gdk_color = window_style->bg[GTK_STATE_SELECTED];
1235  const GdkColor base_color = window_style->base[GTK_STATE_NORMAL];
1236
1237  GtkStyle* label_style = gtk_rc_get_style(fake_label_.get());
1238  const GdkColor text_color = label_style->fg[GTK_STATE_NORMAL];
1239
1240  PickButtonTintFromColors(accent_gdk_color, text_color, base_color, tint);
1241}
1242
1243void Gtk2UI::GetNormalEntryForegroundHSL(color_utils::HSL* tint) const {
1244  GtkStyle* window_style = gtk_rc_get_style(fake_window_);
1245  const GdkColor accent_gdk_color = window_style->bg[GTK_STATE_SELECTED];
1246
1247  GtkStyle* style = gtk_rc_get_style(fake_entry_.get());
1248  const GdkColor text_color = style->text[GTK_STATE_NORMAL];
1249  const GdkColor base_color = style->base[GTK_STATE_NORMAL];
1250
1251  PickButtonTintFromColors(accent_gdk_color, text_color, base_color, tint);
1252}
1253
1254void Gtk2UI::GetSelectedEntryForegroundHSL(color_utils::HSL* tint) const {
1255  // The simplest of all the tints. We just use the selected text in the entry
1256  // since the icons tinted this way will only be displayed against
1257  // base[GTK_STATE_SELECTED].
1258  GtkStyle* style = gtk_rc_get_style(fake_entry_.get());
1259  const GdkColor color = style->text[GTK_STATE_SELECTED];
1260  color_utils::SkColorToHSL(GdkColorToSkColor(color), tint);
1261}
1262
1263SkBitmap Gtk2UI::DrawGtkButtonBorder(int gtk_state,
1264                                     bool focused,
1265                                     int width,
1266                                     int height) const {
1267  // Create a temporary GTK button to snapshot
1268  GtkWidget* window = gtk_offscreen_window_new();
1269  GtkWidget* button = gtk_button_new();
1270  gtk_widget_set_size_request(button, width, height);
1271  gtk_container_add(GTK_CONTAINER(window), button);
1272  gtk_widget_realize(window);
1273  gtk_widget_realize(button);
1274  gtk_widget_show(button);
1275  gtk_widget_show(window);
1276
1277  if (focused) {
1278    // We can't just use gtk_widget_grab_focus() here because that sets
1279    // gtk_widget_is_focus(), but not gtk_widget_has_focus(), which is what the
1280    // GtkButton's paint checks.
1281    GTK_WIDGET_SET_FLAGS(button, GTK_HAS_FOCUS);
1282  }
1283
1284  gtk_widget_set_state(button, static_cast<GtkStateType>(gtk_state));
1285
1286  GdkPixmap* pixmap = gtk_widget_get_snapshot(button, NULL);
1287  int w, h;
1288  gdk_drawable_get_size(GDK_DRAWABLE(pixmap), &w, &h);
1289  DCHECK_EQ(w, width);
1290  DCHECK_EQ(h, height);
1291
1292  // We render the Pixmap to a Pixbuf. This can be slow, as we're scrapping
1293  // bits from X.
1294  GdkColormap* colormap = gdk_drawable_get_colormap(pixmap);
1295  GdkPixbuf* pixbuf = gdk_pixbuf_get_from_drawable(NULL,
1296                                                   GDK_DRAWABLE(pixmap),
1297                                                   colormap,
1298                                                   0, 0, 0, 0, w, h);
1299
1300  // Finally, we convert our pixbuf into a type we can use.
1301  SkBitmap border = GdkPixbufToImageSkia(pixbuf);
1302  g_object_unref(pixbuf);
1303  g_object_unref(pixmap);
1304  gtk_widget_destroy(window);
1305
1306  return border;
1307}
1308
1309gfx::Insets Gtk2UI::GetButtonInsets() const {
1310  return button_insets_;
1311}
1312
1313void Gtk2UI::AddGtkBorder(Gtk2Border* border) {
1314  border_list_.AddObserver(border);
1315}
1316
1317void Gtk2UI::RemoveGtkBorder(Gtk2Border* border) {
1318  border_list_.RemoveObserver(border);
1319}
1320
1321void Gtk2UI::UpdateButtonInsets() {
1322  GtkWidget* window = gtk_offscreen_window_new();
1323  GtkWidget* button = gtk_button_new();
1324  gtk_container_add(GTK_CONTAINER(window), button);
1325
1326  GtkBorder* border = NULL;
1327  gtk_widget_style_get(GTK_WIDGET(button),
1328                       "default-border",
1329                       &border,
1330                       NULL);
1331
1332  gfx::Insets insets;
1333  if (border) {
1334    button_insets_ = gfx::Insets(border->top, border->left,
1335                                 border->bottom, border->right);
1336    gtk_border_free(border);
1337  } else {
1338    // Defined in gtkbutton.c:
1339    button_insets_ = gfx::Insets(1, 1, 1, 1);
1340  }
1341
1342  gtk_widget_destroy(window);
1343}
1344
1345void Gtk2UI::ClearAllThemeData() {
1346  gtk_images_.clear();
1347}
1348
1349}  // namespace libgtk2ui
1350
1351views::LinuxUI* BuildGtk2UI() {
1352  return new libgtk2ui::Gtk2UI;
1353}
1354