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
20namespace {
21
22// Theme colors returned by GetSystemColor().
23const SkColor kInvalidColorIdColor = SkColorSetRGB(255, 0, 128);
24
25const GdkColor kURLTextColor = GDK_COLOR_RGB(0x00, 0x88, 0x00);
26
27GdkColor 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.
39GdkColor 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.
68GdkColor 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
99namespace libgtk2ui {
100
101// static
102NativeThemeGtk2* NativeThemeGtk2::instance() {
103  CR_DEFINE_STATIC_LOCAL(NativeThemeGtk2, s_native_theme, ());
104  return &s_native_theme;
105}
106
107NativeThemeGtk2::NativeThemeGtk2()
108    : fake_window_(NULL),
109      fake_tooltip_(NULL),
110      fake_menu_item_(NULL) {
111}
112
113NativeThemeGtk2::~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
126gfx::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
135void 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
153SkColor 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
160void 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
185void 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
210GdkColor 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
384GtkWidget* 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
393GtkStyle* NativeThemeGtk2::GetWindowStyle() const {
394  return gtk_rc_get_style(GetRealizedWindow());
395}
396
397GtkStyle* 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
409GtkStyle* 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
416GtkStyle* 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
423GtkStyle* 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
430GtkStyle* 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
439GtkStyle* 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
445GtkStyle* 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
457void 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