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