blob: 15d0b7758ac39f0edfbfbff86130ad1d8a73def1 [file] [log] [blame]
// Copyright (c) 2009 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "chrome/browser/gtk/gtk_theme_provider.h"
#include <gtk/gtk.h>
#include "app/gfx/color_utils.h"
#include "base/gfx/gtk_util.h"
#include "chrome/browser/metrics/user_metrics.h"
#include "chrome/browser/profile.h"
#include "chrome/browser/gtk/cairo_cached_surface.h"
#include "chrome/browser/gtk/gtk_chrome_button.h"
#include "chrome/common/pref_names.h"
#include "chrome/common/notification_details.h"
#include "chrome/common/notification_service.h"
#include "chrome/common/notification_source.h"
#include "chrome/common/notification_type.h"
#include "grit/theme_resources.h"
#include "skia/ext/skia_utils_gtk.h"
#include "third_party/skia/include/core/SkBitmap.h"
#include "third_party/skia/include/core/SkCanvas.h"
#include "third_party/skia/include/core/SkColor.h"
#include "grit/app_resources.h"
//#include "grit/generated_resources.h"
#include "grit/theme_resources.h"
namespace {
// The size of the rendered toolbar image.
const int kToolbarImageWidth = 64;
const int kToolbarImageHeight = 128;
const color_utils::HSL kExactColor = { -1, -1, -1 };
const color_utils::HSL kDefaultFrameShift = { -1, -1, 0.4 };
// Values used as the new luminance and saturation values in the inactive tab
// text color.
const double kDarkInactiveLuminance = 0.85;
const double kLightInactiveLuminance = 0.15;
const double kHeavyInactiveSaturation = 0.7;
const double kLightInactiveSaturation = 0.3;
// Minimum difference between the toolbar and the button color before we try a
// different color.
const double kMinimumLuminanceDifference = 0.1;
// Number of times that the background color should be counted when trying to
// calculate the border color in GTK theme mode.
const int kBgWeight = 3;
// Converts a GdkColor to a SkColor.
SkColor GdkToSkColor(GdkColor* color) {
return SkColorSetRGB(color->red >> 8,
color->green >> 8,
color->blue >> 8);
}
} // namespace
GtkWidget* GtkThemeProvider::icon_widget_ = NULL;
GdkPixbuf* GtkThemeProvider::default_folder_icon_ = NULL;
GdkPixbuf* GtkThemeProvider::default_bookmark_icon_ = NULL;
// static
GtkThemeProvider* GtkThemeProvider::GetFrom(Profile* profile) {
return static_cast<GtkThemeProvider*>(profile->GetThemeProvider());
}
GtkThemeProvider::GtkThemeProvider()
: BrowserThemeProvider(),
fake_window_(gtk_window_new(GTK_WINDOW_TOPLEVEL)) {
fake_label_.Own(gtk_label_new(""));
// Only realized widgets receive style-set notifications, which we need to
// broadcast new theme images and colors.
gtk_widget_realize(fake_window_);
g_signal_connect(fake_window_, "style-set", G_CALLBACK(&OnStyleSet), this);
}
GtkThemeProvider::~GtkThemeProvider() {
profile()->GetPrefs()->RemovePrefObserver(prefs::kUsesSystemTheme, this);
gtk_widget_destroy(fake_window_);
fake_label_.Destroy();
FreePerDisplaySurfaces();
// Disconnect from the destroy signal of any redisual widgets in
// |chrome_buttons_|.
for (std::vector<GtkWidget*>::iterator it = chrome_buttons_.begin();
it != chrome_buttons_.end(); ++it) {
gtk_signal_disconnect_by_data(GTK_OBJECT(*it), this);
}
}
void GtkThemeProvider::Init(Profile* profile) {
profile->GetPrefs()->AddPrefObserver(prefs::kUsesSystemTheme, this);
use_gtk_ = profile->GetPrefs()->GetBoolean(prefs::kUsesSystemTheme);
BrowserThemeProvider::Init(profile);
}
void GtkThemeProvider::InitThemesFor(NotificationObserver* observer) {
observer->Observe(NotificationType::BROWSER_THEME_CHANGED,
Source<ThemeProvider>(this),
NotificationService::NoDetails());
}
void GtkThemeProvider::SetTheme(Extension* extension) {
profile()->GetPrefs()->SetBoolean(prefs::kUsesSystemTheme, false);
BrowserThemeProvider::SetTheme(extension);
}
void GtkThemeProvider::UseDefaultTheme() {
profile()->GetPrefs()->SetBoolean(prefs::kUsesSystemTheme, false);
BrowserThemeProvider::UseDefaultTheme();
}
void GtkThemeProvider::SetNativeTheme() {
profile()->GetPrefs()->SetBoolean(prefs::kUsesSystemTheme, true);
ClearAllThemeData();
LoadGtkValues();
NotifyThemeChanged();
}
void GtkThemeProvider::Observe(NotificationType type,
const NotificationSource& source,
const NotificationDetails& details) {
if (type == NotificationType::PREF_CHANGED) {
std::wstring key = *Details<std::wstring>(details).ptr();
if (key == prefs::kUsesSystemTheme) {
use_gtk_ = profile()->GetPrefs()->GetBoolean(prefs::kUsesSystemTheme);
}
}
}
GtkWidget* GtkThemeProvider::BuildChromeButton() {
GtkWidget* button = gtk_chrome_button_new();
gtk_chrome_button_set_use_gtk_rendering(GTK_CHROME_BUTTON(button), use_gtk_);
chrome_buttons_.push_back(button);
g_signal_connect(button, "destroy", G_CALLBACK(OnDestroyChromeButton),
this);
return button;
}
bool GtkThemeProvider::UseGtkTheme() {
return use_gtk_;
}
GdkColor GtkThemeProvider::GetGdkColor(int id) {
return skia::SkColorToGdkColor(GetColor(id));
}
GdkColor GtkThemeProvider::GetBorderColor() {
GtkStyle* style = gtk_rc_get_style(fake_window_);
// Creates a weighted average between the text and base color where
// the base color counts more than once.
GdkColor color;
color.pixel = 0;
color.red = (style->text[GTK_STATE_NORMAL].red +
(style->bg[GTK_STATE_NORMAL].red * kBgWeight)) /
(1 + kBgWeight);
color.green = (style->text[GTK_STATE_NORMAL].green +
(style->bg[GTK_STATE_NORMAL].green * kBgWeight)) /
(1 + kBgWeight);
color.blue = (style->text[GTK_STATE_NORMAL].blue +
(style->bg[GTK_STATE_NORMAL].blue * kBgWeight)) /
(1 + kBgWeight);
return color;
}
CairoCachedSurface* GtkThemeProvider::GetSurfaceNamed(
int id, GtkWidget* widget_on_display) {
GdkDisplay* display = gtk_widget_get_display(widget_on_display);
CairoCachedSurfaceMap& surface_map = per_display_surfaces_[display];
// Check to see if we already have the pixbuf in the cache.
CairoCachedSurfaceMap::const_iterator found = surface_map.find(id);
if (found != surface_map.end())
return found->second;
GdkPixbuf* pixbuf = GetPixbufNamed(id);
CairoCachedSurface* surface = new CairoCachedSurface;
surface->UsePixbuf(pixbuf);
surface_map[id] = surface;
return surface;
}
// static
GdkPixbuf* GtkThemeProvider::GetFolderIcon(bool native) {
if (native) {
if (!icon_widget_)
icon_widget_ = gtk_fixed_new();
// We never release our ref, so we will leak this on program shutdown.
if (!default_folder_icon_) {
default_folder_icon_ =
gtk_widget_render_icon(icon_widget_, GTK_STOCK_DIRECTORY,
GTK_ICON_SIZE_MENU, NULL);
}
if (default_folder_icon_)
return default_folder_icon_;
}
ResourceBundle& rb = ResourceBundle::GetSharedInstance();
static GdkPixbuf* default_folder_icon_ = rb.GetPixbufNamed(
IDR_BOOKMARK_BAR_FOLDER);
return default_folder_icon_;
}
// static
GdkPixbuf* GtkThemeProvider::GetDefaultFavicon(bool native) {
if (native) {
if (!icon_widget_)
icon_widget_ = gtk_fixed_new();
// We never release our ref, so we will leak this on program shutdown.
if (!default_bookmark_icon_) {
default_bookmark_icon_ =
gtk_widget_render_icon(icon_widget_, GTK_STOCK_FILE,
GTK_ICON_SIZE_MENU, NULL);
}
if (default_bookmark_icon_)
return default_bookmark_icon_;
}
ResourceBundle& rb = ResourceBundle::GetSharedInstance();
static GdkPixbuf* default_bookmark_icon_ = rb.GetPixbufNamed(
IDR_DEFAULT_FAVICON);
return default_bookmark_icon_;
}
void GtkThemeProvider::LoadThemePrefs() {
if (use_gtk_) {
LoadGtkValues();
} else {
BrowserThemeProvider::LoadThemePrefs();
}
}
void GtkThemeProvider::NotifyThemeChanged() {
BrowserThemeProvider::NotifyThemeChanged();
// Notify all GtkChromeButtons of their new rendering mode:
for (std::vector<GtkWidget*>::iterator it = chrome_buttons_.begin();
it != chrome_buttons_.end(); ++it) {
gtk_chrome_button_set_use_gtk_rendering(
GTK_CHROME_BUTTON(*it), use_gtk_);
}
}
SkBitmap* GtkThemeProvider::LoadThemeBitmap(int id) {
if (id == IDR_THEME_TOOLBAR && use_gtk_) {
GtkStyle* style = gtk_rc_get_style(fake_window_);
GdkColor* color = &style->bg[GTK_STATE_NORMAL];
SkBitmap* bitmap = new SkBitmap;
bitmap->setConfig(SkBitmap::kARGB_8888_Config,
kToolbarImageWidth, kToolbarImageHeight);
bitmap->allocPixels();
bitmap->eraseRGB(color->red >> 8, color->green >> 8, color->blue >> 8);
return bitmap;
} else {
return BrowserThemeProvider::LoadThemeBitmap(id);
}
}
void GtkThemeProvider::SaveThemeBitmap(const std::string resource_name,
int id) {
if (!use_gtk_) {
// Prevent us from writing out our mostly unused resources in gtk theme
// mode. Simply preventing us from writing this data out in gtk mode isn't
// the best design, but this would probably be a very invasive change on
// all three platforms otherwise.
BrowserThemeProvider::SaveThemeBitmap(resource_name, id);
}
}
void GtkThemeProvider::FreePlatformCaches() {
BrowserThemeProvider::FreePlatformCaches();
FreePerDisplaySurfaces();
}
// static
void GtkThemeProvider::OnStyleSet(GtkWidget* widget,
GtkStyle* previous_style,
GtkThemeProvider* provider) {
GdkPixbuf* default_folder_icon = default_folder_icon_;
GdkPixbuf* default_bookmark_icon = default_bookmark_icon_;
default_folder_icon_ = NULL;
default_bookmark_icon_ = NULL;
if (provider->profile()->GetPrefs()->GetBoolean(prefs::kUsesSystemTheme)) {
provider->ClearAllThemeData();
provider->LoadGtkValues();
provider->NotifyThemeChanged();
}
// Free the old icons only after the theme change notification has gone
// through.
if (default_folder_icon)
g_object_unref(default_folder_icon);
if (default_bookmark_icon)
g_object_unref(default_bookmark_icon);
}
void GtkThemeProvider::LoadGtkValues() {
// Before we start setting images and values, we have to clear out old, stale
// values. (If we don't do this, we'll regress startup time in the case where
// someone installs a heavyweight theme, then goes back to GTK.)
DictionaryValue* pref_images =
profile()->GetPrefs()->GetMutableDictionary(prefs::kCurrentThemeImages);
pref_images->Clear();
GtkStyle* window_style = gtk_rc_get_style(fake_window_);
GtkStyle* label_style = gtk_rc_get_style(fake_label_.get());
GdkColor frame_color = window_style->bg[GTK_STATE_SELECTED];
GdkColor inactive_frame_color = window_style->bg[GTK_STATE_INSENSITIVE];
GdkColor toolbar_color = window_style->bg[GTK_STATE_NORMAL];
GdkColor button_color = window_style->bg[GTK_STATE_SELECTED];
GdkColor label_color = label_style->text[GTK_STATE_NORMAL];
GtkSettings* settings = gtk_settings_get_default();
bool theme_has_frame_color = false;
if (settings) {
GHashTable* color_scheme = NULL;
g_object_get(settings, "color-hash", &color_scheme, NULL);
if (color_scheme) {
// If we have a "gtk-color-scheme" set in this theme, mine it for hints
// about what we should actually set the frame color to.
GdkColor* color = NULL;
if ((color = static_cast<GdkColor*>(
g_hash_table_lookup(color_scheme, "frame_color")))) {
frame_color = *color;
theme_has_frame_color = true;
}
if ((color = static_cast<GdkColor*>(
g_hash_table_lookup(color_scheme, "inactive_frame_color")))) {
inactive_frame_color = *color;
}
}
}
if (!theme_has_frame_color) {
// If the theme's gtkrc doesn't explicitly tell us to use a specific frame
// color, change the luminosity of the frame color downwards to 80% of what
// it currently is. This is in a futile attempt to match the default
// metacity and xfwm themes.
SkColor shifted = color_utils::HSLShift(GdkToSkColor(&frame_color),
kDefaultFrameShift);
frame_color.pixel = 0;
frame_color.red = SkColorGetR(shifted) * kSkiaToGDKMultiplier;
frame_color.green = SkColorGetG(shifted) * kSkiaToGDKMultiplier;
frame_color.blue = SkColorGetB(shifted) * kSkiaToGDKMultiplier;
}
// By default, the button tint color is the background selection color. But
// this can be unreadable in some dark themes, so we set a minimum contrast
// between the button color and the toolbar color.
color_utils::HSL button_hsl;
color_utils::SkColorToHSL(GdkToSkColor(&button_color), &button_hsl);
color_utils::HSL toolbar_hsl;
color_utils::SkColorToHSL(GdkToSkColor(&toolbar_color), &toolbar_hsl);
double hsl_difference = fabs(button_hsl.l - toolbar_hsl.l);
if (hsl_difference <= kMinimumLuminanceDifference) {
// Not enough contrast. Try the text color instead.
color_utils::HSL label_hsl;
color_utils::SkColorToHSL(GdkToSkColor(&label_color), &label_hsl);
double label_difference = fabs(label_hsl.l - toolbar_hsl.l);
if (label_difference >= kMinimumLuminanceDifference) {
button_color = label_color;
}
}
SetThemeColorFromGtk(kColorFrame, &frame_color);
// Skip COLOR_FRAME_INACTIVE and the incognito colors, as they will be
// autogenerated from tints.
SetThemeColorFromGtk(kColorToolbar, &toolbar_color);
SetThemeColorFromGtk(kColorTabText, &label_color);
SetThemeColorFromGtk(kColorBookmarkText, &label_color);
SetThemeColorFromGtk(kColorControlBackground,
&window_style->bg[GTK_STATE_NORMAL]);
SetThemeColorFromGtk(kColorButtonBackground,
&window_style->bg[GTK_STATE_NORMAL]);
SetThemeTintFromGtk(kTintButtons, &button_color,
kDefaultTintButtons);
SetThemeTintFromGtk(kTintFrame, &frame_color,
kDefaultTintFrame);
SetThemeTintFromGtk(kTintFrameIncognito,
&frame_color,
kDefaultTintFrameIncognito);
SetThemeTintFromGtk(kTintBackgroundTab,
&frame_color,
kDefaultTintBackgroundTab);
// The inactive frame color never occurs naturally in the theme, as it is a
// tinted version of |frame_color|. We generate another color based on the
// background tab color, with the lightness and saturation moved in the
// opposite direction. (We don't touch the hue, since there should be subtle
// hints of the color in the text.)
color_utils::HSL inactive_tab_text_hsl = GetTint(TINT_BACKGROUND_TAB);
if (inactive_tab_text_hsl.l < 0.5)
inactive_tab_text_hsl.l = kDarkInactiveLuminance;
else
inactive_tab_text_hsl.l = kLightInactiveLuminance;
if (inactive_tab_text_hsl.s < 0.5)
inactive_tab_text_hsl.s = kHeavyInactiveSaturation;
else
inactive_tab_text_hsl.s = kLightInactiveSaturation;
SetColor(kColorBackgroundTabText,
color_utils::HSLToSkColor(inactive_tab_text_hsl, 255));
// The inactive color/tint is special: We *must* use the exact insensitive
// color for all inactive windows, otherwise we end up neon pink half the
// time.
SetThemeColorFromGtk(kColorFrameInactive, &inactive_frame_color);
SetThemeTintFromGtk(kTintFrameInactive, &inactive_frame_color,
kExactColor);
SetThemeTintFromGtk(kTintFrameIncognitoInactive, &inactive_frame_color,
kExactColor);
force_process_images();
GenerateFrameColors();
GenerateFrameImages();
}
void GtkThemeProvider::SetThemeColorFromGtk(const char* id, GdkColor* color) {
SetColor(id, GdkToSkColor(color));
}
void GtkThemeProvider::SetThemeTintFromGtk(
const char* id,
GdkColor* color,
const color_utils::HSL& default_tint) {
color_utils::HSL hsl;
color_utils::SkColorToHSL(GdkToSkColor(color), &hsl);
if (default_tint.s != -1)
hsl.s = default_tint.s;
if (default_tint.l != -1)
hsl.l = default_tint.l;
SetTint(id, hsl);
}
void GtkThemeProvider::FreePerDisplaySurfaces() {
for (PerDisplaySurfaceMap::iterator it = per_display_surfaces_.begin();
it != per_display_surfaces_.end(); ++it) {
for (CairoCachedSurfaceMap::iterator jt = it->second.begin();
jt != it->second.end(); ++jt) {
delete jt->second;
}
}
per_display_surfaces_.clear();
}
void GtkThemeProvider::OnDestroyChromeButton(GtkWidget* button,
GtkThemeProvider* provider) {
std::vector<GtkWidget*>::iterator it =
find(provider->chrome_buttons_.begin(), provider->chrome_buttons_.end(),
button);
if (it != provider->chrome_buttons_.end())
provider->chrome_buttons_.erase(it);
}