Home | History | Annotate | Download | only in libgtk2ui
      1 // Copyright (c) 2013 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/native_theme_gtk2.h"
      6 
      7 #include <gtk/gtk.h>
      8 
      9 #include "chrome/browser/ui/libgtk2ui/chrome_gtk_menu_subclasses.h"
     10 #include "chrome/browser/ui/libgtk2ui/gtk2_ui.h"
     11 #include "chrome/browser/ui/libgtk2ui/gtk2_util.h"
     12 #include "chrome/browser/ui/libgtk2ui/skia_utils_gtk2.h"
     13 #include "ui/gfx/color_utils.h"
     14 #include "ui/gfx/path.h"
     15 #include "ui/gfx/rect.h"
     16 #include "ui/gfx/size.h"
     17 #include "ui/gfx/skia_util.h"
     18 #include "ui/native_theme/common_theme.h"
     19 
     20 namespace {
     21 
     22 // Theme colors returned by GetSystemColor().
     23 const SkColor kInvalidColorIdColor = SkColorSetRGB(255, 0, 128);
     24 
     25 const GdkColor kURLTextColor = GDK_COLOR_RGB(0x00, 0x88, 0x00);
     26 
     27 GdkColor GdkAlphaBlend(GdkColor foreground,
     28                        GdkColor background,
     29                        SkAlpha alpha) {
     30   return libgtk2ui::SkColorToGdkColor(
     31       color_utils::AlphaBlend(libgtk2ui::GdkColorToSkColor(foreground),
     32                               libgtk2ui::GdkColorToSkColor(background), alpha));
     33 }
     34 
     35 // Generates the normal URL color, a green color used in unhighlighted URL
     36 // text. It is a mix of |kURLTextColor| and the current text color.  Unlike the
     37 // selected text color, it is more important to match the qualities of the
     38 // foreground typeface color instead of taking the background into account.
     39 GdkColor NormalURLColor(GdkColor foreground) {
     40   color_utils::HSL fg_hsl;
     41   color_utils::SkColorToHSL(libgtk2ui::GdkColorToSkColor(foreground), &fg_hsl);
     42 
     43   color_utils::HSL hue_hsl;
     44   color_utils::SkColorToHSL(libgtk2ui::GdkColorToSkColor(kURLTextColor),
     45                             &hue_hsl);
     46 
     47   // Only allow colors that have a fair amount of saturation in them (color vs
     48   // white). This means that our output color will always be fairly green.
     49   double s = std::max(0.5, fg_hsl.s);
     50 
     51   // Make sure the luminance is at least as bright as the |kURLTextColor| green
     52   // would be if we were to use that.
     53   double l;
     54   if (fg_hsl.l < hue_hsl.l)
     55     l = hue_hsl.l;
     56   else
     57     l = (fg_hsl.l + hue_hsl.l) / 2;
     58 
     59   color_utils::HSL output = { hue_hsl.h, s, l };
     60   return libgtk2ui::SkColorToGdkColor(color_utils::HSLToSkColor(output, 255));
     61 }
     62 
     63 // Generates the selected URL color, a green color used on URL text in the
     64 // currently highlighted entry in the autocomplete popup. It's a mix of
     65 // |kURLTextColor|, the current text color, and the background color (the
     66 // select highlight). It is more important to contrast with the background
     67 // saturation than to look exactly like the foreground color.
     68 GdkColor SelectedURLColor(GdkColor foreground, GdkColor background) {
     69   color_utils::HSL fg_hsl;
     70   color_utils::SkColorToHSL(libgtk2ui::GdkColorToSkColor(foreground),
     71                             &fg_hsl);
     72 
     73   color_utils::HSL bg_hsl;
     74   color_utils::SkColorToHSL(libgtk2ui::GdkColorToSkColor(background),
     75                             &bg_hsl);
     76 
     77   color_utils::HSL hue_hsl;
     78   color_utils::SkColorToHSL(libgtk2ui::GdkColorToSkColor(kURLTextColor),
     79                             &hue_hsl);
     80 
     81   // The saturation of the text should be opposite of the background, clamped
     82   // to 0.2-0.8. We make sure it's greater than 0.2 so there's some color, but
     83   // less than 0.8 so it's not the oversaturated neon-color.
     84   double opposite_s = 1 - bg_hsl.s;
     85   double s = std::max(0.2, std::min(0.8, opposite_s));
     86 
     87   // The luminance should match the luminance of the foreground text.  Again,
     88   // we clamp so as to have at some amount of color (green) in the text.
     89   double opposite_l = fg_hsl.l;
     90   double l = std::max(0.1, std::min(0.9, opposite_l));
     91 
     92   color_utils::HSL output = { hue_hsl.h, s, l };
     93   return libgtk2ui::SkColorToGdkColor(color_utils::HSLToSkColor(output, 255));
     94 }
     95 
     96 }  // namespace
     97 
     98 
     99 namespace libgtk2ui {
    100 
    101 // static
    102 NativeThemeGtk2* NativeThemeGtk2::instance() {
    103   CR_DEFINE_STATIC_LOCAL(NativeThemeGtk2, s_native_theme, ());
    104   return &s_native_theme;
    105 }
    106 
    107 NativeThemeGtk2::NativeThemeGtk2()
    108     : fake_window_(NULL),
    109       fake_tooltip_(NULL),
    110       fake_menu_item_(NULL) {
    111 }
    112 
    113 NativeThemeGtk2::~NativeThemeGtk2() {
    114   if (fake_window_)
    115     gtk_widget_destroy(fake_window_);
    116   if (fake_tooltip_)
    117     gtk_widget_destroy(fake_tooltip_);
    118 
    119   fake_entry_.Destroy();
    120   fake_label_.Destroy();
    121   fake_button_.Destroy();
    122   fake_tree_.Destroy();
    123   fake_menu_.Destroy();
    124 }
    125 
    126 gfx::Size NativeThemeGtk2::GetPartSize(Part part,
    127                                        State state,
    128                                        const ExtraParams& extra) const {
    129   if (part == kComboboxArrow)
    130     return gfx::Size(12, 12);
    131 
    132   return ui::NativeThemeBase::GetPartSize(part, state, extra);
    133 }
    134 
    135 void NativeThemeGtk2::Paint(SkCanvas* canvas,
    136                             Part part,
    137                             State state,
    138                             const gfx::Rect& rect,
    139                             const ExtraParams& extra) const {
    140   if (rect.IsEmpty())
    141     return;
    142 
    143   switch (part) {
    144     case kComboboxArrow:
    145       PaintComboboxArrow(canvas, GetGtkState(state), rect);
    146       return;
    147 
    148     default:
    149       NativeThemeBase::Paint(canvas, part, state, rect, extra);
    150   }
    151 }
    152 
    153 SkColor NativeThemeGtk2::GetSystemColor(ColorId color_id) const {
    154   if (color_id == kColorId_BlueButtonShadowColor)
    155     return SK_ColorTRANSPARENT;
    156 
    157   return GdkColorToSkColor(GetSystemGdkColor(color_id));
    158 }
    159 
    160 void NativeThemeGtk2::PaintMenuPopupBackground(
    161     SkCanvas* canvas,
    162     const gfx::Size& size,
    163     const MenuBackgroundExtraParams& menu_background) const {
    164   if (menu_background.corner_radius > 0) {
    165     SkPaint paint;
    166     paint.setStyle(SkPaint::kFill_Style);
    167     paint.setFlags(SkPaint::kAntiAlias_Flag);
    168     paint.setColor(GetSystemColor(kColorId_MenuBackgroundColor));
    169 
    170     gfx::Path path;
    171     SkRect rect = SkRect::MakeWH(SkIntToScalar(size.width()),
    172                                  SkIntToScalar(size.height()));
    173     SkScalar radius = SkIntToScalar(menu_background.corner_radius);
    174     SkScalar radii[8] = {radius, radius, radius, radius,
    175                          radius, radius, radius, radius};
    176     path.addRoundRect(rect, radii);
    177 
    178     canvas->drawPath(path, paint);
    179   } else {
    180     canvas->drawColor(GetSystemColor(kColorId_MenuBackgroundColor),
    181                       SkXfermode::kSrc_Mode);
    182   }
    183 }
    184 
    185 void NativeThemeGtk2::PaintMenuItemBackground(
    186     SkCanvas* canvas,
    187     State state,
    188     const gfx::Rect& rect,
    189     const MenuListExtraParams& menu_list) const {
    190   SkColor color;
    191   SkPaint paint;
    192   switch (state) {
    193     case NativeTheme::kNormal:
    194     case NativeTheme::kDisabled:
    195       color = GetSystemColor(NativeTheme::kColorId_MenuBackgroundColor);
    196       paint.setColor(color);
    197       break;
    198     case NativeTheme::kHovered:
    199       color = GetSystemColor(
    200           NativeTheme::kColorId_FocusedMenuItemBackgroundColor);
    201       paint.setColor(color);
    202       break;
    203     default:
    204       NOTREACHED() << "Invalid state " << state;
    205       break;
    206   }
    207   canvas->drawRect(gfx::RectToSkRect(rect), paint);
    208 }
    209 
    210 GdkColor NativeThemeGtk2::GetSystemGdkColor(ColorId color_id) const {
    211   switch (color_id) {
    212     // Windows
    213     case kColorId_WindowBackground:
    214       return GetWindowStyle()->bg[GTK_STATE_NORMAL];
    215 
    216     // Dialogs
    217     case kColorId_DialogBackground:
    218       return GetWindowStyle()->bg[GTK_STATE_NORMAL];
    219 
    220     // FocusableBorder
    221     case kColorId_FocusedBorderColor:
    222       return GetEntryStyle()->bg[GTK_STATE_SELECTED];
    223     case kColorId_UnfocusedBorderColor:
    224       return GetEntryStyle()->text_aa[GTK_STATE_NORMAL];
    225 
    226     // MenuItem
    227     case kColorId_EnabledMenuItemForegroundColor:
    228     case kColorId_DisabledEmphasizedMenuItemForegroundColor:
    229       return GetMenuItemStyle()->text[GTK_STATE_NORMAL];
    230     case kColorId_DisabledMenuItemForegroundColor:
    231       return GetMenuItemStyle()->text[GTK_STATE_INSENSITIVE];
    232     case kColorId_SelectedMenuItemForegroundColor:
    233       return GetMenuItemStyle()->text[GTK_STATE_SELECTED];
    234     case kColorId_FocusedMenuItemBackgroundColor:
    235       return GetMenuItemStyle()->bg[GTK_STATE_SELECTED];
    236     case kColorId_HoverMenuItemBackgroundColor:
    237       return GetMenuItemStyle()->bg[GTK_STATE_PRELIGHT];
    238     case kColorId_FocusedMenuButtonBorderColor:
    239       return GetEntryStyle()->bg[GTK_STATE_NORMAL];
    240     case kColorId_HoverMenuButtonBorderColor:
    241       return GetEntryStyle()->text_aa[GTK_STATE_PRELIGHT];
    242     case kColorId_MenuBorderColor:
    243     case kColorId_EnabledMenuButtonBorderColor:
    244     case kColorId_MenuSeparatorColor: {
    245       return GetMenuItemStyle()->text[GTK_STATE_INSENSITIVE];
    246     }
    247     case kColorId_MenuBackgroundColor:
    248       return GetMenuStyle()->bg[GTK_STATE_NORMAL];
    249 
    250     // Label
    251     case kColorId_LabelEnabledColor:
    252       return GetLabelStyle()->text[GTK_STATE_NORMAL];
    253     case kColorId_LabelDisabledColor:
    254       return GetLabelStyle()->text[GTK_STATE_INSENSITIVE];
    255     case kColorId_LabelBackgroundColor:
    256       return GetWindowStyle()->bg[GTK_STATE_NORMAL];
    257 
    258     // Button
    259     case kColorId_ButtonBackgroundColor:
    260       return GetButtonStyle()->bg[GTK_STATE_NORMAL];
    261     case kColorId_ButtonEnabledColor:
    262     case kColorId_BlueButtonEnabledColor:
    263       return GetButtonStyle()->text[GTK_STATE_NORMAL];
    264     case kColorId_ButtonDisabledColor:
    265     case kColorId_BlueButtonDisabledColor:
    266       return GetButtonStyle()->text[GTK_STATE_INSENSITIVE];
    267     case kColorId_ButtonHighlightColor:
    268       return GetButtonStyle()->base[GTK_STATE_SELECTED];
    269     case kColorId_ButtonHoverColor:
    270     case kColorId_BlueButtonHoverColor:
    271       return GetButtonStyle()->text[GTK_STATE_PRELIGHT];
    272     case kColorId_ButtonHoverBackgroundColor:
    273       return GetButtonStyle()->bg[GTK_STATE_PRELIGHT];
    274     case kColorId_BlueButtonPressedColor:
    275       return GetButtonStyle()->text[GTK_STATE_ACTIVE];
    276     case kColorId_BlueButtonShadowColor:
    277       // Should be handled in GetSystemColor().
    278       NOTREACHED();
    279       return GetButtonStyle()->text[GTK_STATE_NORMAL];
    280 
    281     // Textfield
    282     case kColorId_TextfieldDefaultColor:
    283       return GetEntryStyle()->text[GTK_STATE_NORMAL];
    284     case kColorId_TextfieldDefaultBackground:
    285       return GetEntryStyle()->base[GTK_STATE_NORMAL];
    286     case kColorId_TextfieldReadOnlyColor:
    287       return GetEntryStyle()->text[GTK_STATE_INSENSITIVE];
    288     case kColorId_TextfieldReadOnlyBackground:
    289       return GetEntryStyle()->base[GTK_STATE_INSENSITIVE];
    290     case kColorId_TextfieldSelectionColor:
    291       return GetEntryStyle()->text[GTK_STATE_SELECTED];
    292     case kColorId_TextfieldSelectionBackgroundFocused:
    293       return GetEntryStyle()->base[GTK_STATE_SELECTED];
    294 
    295     // Tooltips
    296     case kColorId_TooltipBackground:
    297       return GetTooltipStyle()->bg[GTK_STATE_NORMAL];
    298     case kColorId_TooltipText:
    299       return GetTooltipStyle()->fg[GTK_STATE_NORMAL];
    300 
    301     // Trees and Tables (implemented on GTK using the same class)
    302     case kColorId_TableBackground:
    303     case kColorId_TreeBackground:
    304       return GetTreeStyle()->bg[GTK_STATE_NORMAL];
    305     case kColorId_TableText:
    306     case kColorId_TreeText:
    307       return GetTreeStyle()->text[GTK_STATE_NORMAL];
    308     case kColorId_TableSelectedText:
    309     case kColorId_TableSelectedTextUnfocused:
    310     case kColorId_TreeSelectedText:
    311     case kColorId_TreeSelectedTextUnfocused:
    312       return GetTreeStyle()->text[GTK_STATE_SELECTED];
    313     case kColorId_TableSelectionBackgroundFocused:
    314     case kColorId_TableSelectionBackgroundUnfocused:
    315     case kColorId_TreeSelectionBackgroundFocused:
    316     case kColorId_TreeSelectionBackgroundUnfocused:
    317       return GetTreeStyle()->bg[GTK_STATE_SELECTED];
    318     case kColorId_TreeArrow:
    319       return GetTreeStyle()->fg[GTK_STATE_NORMAL];
    320     case kColorId_TableGroupingIndicatorColor:
    321       return GetTreeStyle()->text_aa[GTK_STATE_NORMAL];
    322 
    323       // Results Table
    324     case kColorId_ResultsTableNormalBackground:
    325       return GetEntryStyle()->base[GTK_STATE_NORMAL];
    326     case kColorId_ResultsTableHoveredBackground: {
    327       GtkStyle* entry_style = GetEntryStyle();
    328       return GdkAlphaBlend(
    329           entry_style->base[GTK_STATE_NORMAL],
    330           entry_style->base[GTK_STATE_SELECTED], 0x80);
    331     }
    332     case kColorId_ResultsTableSelectedBackground:
    333       return GetEntryStyle()->base[GTK_STATE_SELECTED];
    334     case kColorId_ResultsTableNormalText:
    335     case kColorId_ResultsTableHoveredText:
    336       return GetEntryStyle()->text[GTK_STATE_NORMAL];
    337     case kColorId_ResultsTableSelectedText:
    338       return GetEntryStyle()->text[GTK_STATE_SELECTED];
    339     case kColorId_ResultsTableNormalDimmedText:
    340     case kColorId_ResultsTableHoveredDimmedText: {
    341       GtkStyle* entry_style = GetEntryStyle();
    342       return GdkAlphaBlend(
    343           entry_style->text[GTK_STATE_NORMAL],
    344           entry_style->base[GTK_STATE_NORMAL], 0x80);
    345     }
    346     case kColorId_ResultsTableSelectedDimmedText: {
    347       GtkStyle* entry_style = GetEntryStyle();
    348       return GdkAlphaBlend(
    349           entry_style->text[GTK_STATE_SELECTED],
    350           entry_style->base[GTK_STATE_NORMAL], 0x80);
    351     }
    352     case kColorId_ResultsTableNormalUrl:
    353     case kColorId_ResultsTableHoveredUrl: {
    354       return NormalURLColor(GetEntryStyle()->text[GTK_STATE_NORMAL]);
    355     }
    356     case kColorId_ResultsTableSelectedUrl: {
    357       GtkStyle* entry_style = GetEntryStyle();
    358       return SelectedURLColor(entry_style->text[GTK_STATE_SELECTED],
    359                               entry_style->base[GTK_STATE_SELECTED]);
    360     }
    361     case kColorId_ResultsTableNormalDivider: {
    362       GtkStyle* win_style = GetWindowStyle();
    363       return GdkAlphaBlend(win_style->text[GTK_STATE_NORMAL],
    364                            win_style->bg[GTK_STATE_NORMAL], 0x34);
    365     }
    366     case kColorId_ResultsTableHoveredDivider: {
    367       GtkStyle* win_style = GetWindowStyle();
    368       return GdkAlphaBlend(win_style->text[GTK_STATE_PRELIGHT],
    369                            win_style->bg[GTK_STATE_PRELIGHT], 0x34);
    370     }
    371     case kColorId_ResultsTableSelectedDivider: {
    372       GtkStyle* win_style = GetWindowStyle();
    373       return GdkAlphaBlend(win_style->text[GTK_STATE_SELECTED],
    374                            win_style->bg[GTK_STATE_SELECTED], 0x34);
    375     }
    376     case kColorId_NumColors:
    377       NOTREACHED();
    378       break;
    379   }
    380 
    381   return SkColorToGdkColor(kInvalidColorIdColor);
    382 }
    383 
    384 GtkWidget* NativeThemeGtk2::GetRealizedWindow() const {
    385   if (!fake_window_) {
    386     fake_window_ = gtk_window_new(GTK_WINDOW_TOPLEVEL);
    387     gtk_widget_realize(fake_window_);
    388   }
    389 
    390   return fake_window_;
    391 }
    392 
    393 GtkStyle* NativeThemeGtk2::GetWindowStyle() const {
    394   return gtk_rc_get_style(GetRealizedWindow());
    395 }
    396 
    397 GtkStyle* NativeThemeGtk2::GetEntryStyle() const {
    398   if (!fake_entry_.get()) {
    399     fake_entry_.Own(gtk_entry_new());
    400 
    401     // The fake entry needs to be in the window so it can be realized so we can
    402     // use the computed parts of the style.
    403     gtk_container_add(GTK_CONTAINER(GetRealizedWindow()), fake_entry_.get());
    404     gtk_widget_realize(fake_entry_.get());
    405   }
    406   return gtk_rc_get_style(fake_entry_.get());
    407 }
    408 
    409 GtkStyle* NativeThemeGtk2::GetLabelStyle() const {
    410   if (!fake_label_.get())
    411     fake_label_.Own(gtk_label_new(""));
    412 
    413   return gtk_rc_get_style(fake_label_.get());
    414 }
    415 
    416 GtkStyle* NativeThemeGtk2::GetButtonStyle() const {
    417   if (!fake_button_.get())
    418     fake_button_.Own(gtk_button_new());
    419 
    420   return gtk_rc_get_style(fake_button_.get());
    421 }
    422 
    423 GtkStyle* NativeThemeGtk2::GetTreeStyle() const {
    424   if (!fake_tree_.get())
    425     fake_tree_.Own(gtk_tree_view_new());
    426 
    427   return gtk_rc_get_style(fake_tree_.get());
    428 }
    429 
    430 GtkStyle* NativeThemeGtk2::GetTooltipStyle() const {
    431   if (!fake_tooltip_) {
    432     fake_tooltip_ = gtk_window_new(GTK_WINDOW_TOPLEVEL);
    433     gtk_widget_set_name(fake_tooltip_, "gtk-tooltip");
    434     gtk_widget_realize(fake_tooltip_);
    435   }
    436   return gtk_rc_get_style(fake_tooltip_);
    437 }
    438 
    439 GtkStyle* NativeThemeGtk2::GetMenuStyle() const {
    440   if (!fake_menu_.get())
    441     fake_menu_.Own(gtk_custom_menu_new());
    442   return gtk_rc_get_style(fake_menu_.get());
    443 }
    444 
    445 GtkStyle* NativeThemeGtk2::GetMenuItemStyle() const {
    446   if (!fake_menu_item_) {
    447     if (!fake_menu_.get())
    448       fake_menu_.Own(gtk_custom_menu_new());
    449 
    450     fake_menu_item_ = gtk_custom_menu_item_new();
    451     gtk_menu_shell_append(GTK_MENU_SHELL(fake_menu_.get()), fake_menu_item_);
    452   }
    453 
    454   return gtk_rc_get_style(fake_menu_item_);
    455 }
    456 
    457 void NativeThemeGtk2::PaintComboboxArrow(SkCanvas* canvas,
    458                                          GtkStateType state,
    459                                          const gfx::Rect& rect) const {
    460   GdkPixmap* pm = gdk_pixmap_new(gtk_widget_get_window(GetRealizedWindow()),
    461                                  rect.width(),
    462                                  rect.height(),
    463                                  -1);
    464   // Paint the background.
    465   gtk_paint_flat_box(GetWindowStyle(),
    466                      pm,
    467                      state,
    468                      GTK_SHADOW_NONE,
    469                      NULL,
    470                      GetRealizedWindow(),
    471                      NULL, 0, 0, rect.width(), rect.height());
    472   gtk_paint_arrow(GetWindowStyle(),
    473                   pm,
    474                   state,
    475                   GTK_SHADOW_NONE,
    476                   NULL,
    477                   GetRealizedWindow(),
    478                   NULL,
    479                   GTK_ARROW_DOWN,
    480                   true,
    481                   0, 0, rect.width(), rect.height());
    482   GdkPixbuf* pb = gdk_pixbuf_get_from_drawable(NULL,
    483                                                pm,
    484                                                gdk_drawable_get_colormap(pm),
    485                                                0, 0,
    486                                                0, 0,
    487                                                rect.width(), rect.height());
    488   SkBitmap arrow = GdkPixbufToImageSkia(pb);
    489   canvas->drawBitmap(arrow, rect.x(), rect.y());
    490 
    491   g_object_unref(pb);
    492   g_object_unref(pm);
    493 }
    494 
    495 }  // namespace libgtk2ui
    496