1 /* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
2 /* vim:expandtab:shiftwidth=4:tabstop=4:
3 */
4 /* This Source Code Form is subject to the terms of the Mozilla Public
5 * License, v. 2.0. If a copy of the MPL was not distributed with this
6 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
7
8 // for strtod()
9 #include <stdlib.h>
10
11 #include "nsLookAndFeel.h"
12
13 #include <gtk/gtk.h>
14 #include <gdk/gdk.h>
15
16 #include <pango/pango.h>
17 #include <pango/pango-fontmap.h>
18
19 #include <fontconfig/fontconfig.h>
20 #include "gfxPlatformGtk.h"
21 #include "ScreenHelperGTK.h"
22
23 #include "gtkdrawing.h"
24 #include "nsStyleConsts.h"
25 #include "gfxFontConstants.h"
26 #include "WidgetUtils.h"
27 #include "nsWindow.h"
28
29 #include "mozilla/gfx/2D.h"
30
31 #include <cairo-gobject.h>
32 #include "WidgetStyleCache.h"
33 #include "prenv.h"
34
35 using mozilla::LookAndFeel;
36
37 #define GDK_COLOR_TO_NS_RGB(c) \
38 ((nscolor)NS_RGB(c.red >> 8, c.green >> 8, c.blue >> 8))
39 #define GDK_RGBA_TO_NS_RGBA(c) \
40 ((nscolor)NS_RGBA((int)((c).red * 255), (int)((c).green * 255), \
41 (int)((c).blue * 255), (int)((c).alpha * 255)))
42
43 #if !GTK_CHECK_VERSION(3, 12, 0)
44 #define GTK_STATE_FLAG_LINK (static_cast<GtkStateFlags>(1 << 9))
45 #endif
46
nsLookAndFeel()47 nsLookAndFeel::nsLookAndFeel()
48 : nsXPLookAndFeel(),
49 mDefaultFontCached(false),
50 mButtonFontCached(false),
51 mFieldFontCached(false),
52 mMenuFontCached(false),
53 mInitialized(false) {}
54
~nsLookAndFeel()55 nsLookAndFeel::~nsLookAndFeel() {}
56
57 // Modifies color |*aDest| as if a pattern of color |aSource| was painted with
58 // CAIRO_OPERATOR_OVER to a surface with color |*aDest|.
ApplyColorOver(const GdkRGBA & aSource,GdkRGBA * aDest)59 static void ApplyColorOver(const GdkRGBA& aSource, GdkRGBA* aDest) {
60 gdouble sourceCoef = aSource.alpha;
61 gdouble destCoef = aDest->alpha * (1.0 - sourceCoef);
62 gdouble resultAlpha = sourceCoef + destCoef;
63 if (resultAlpha != 0.0) { // don't divide by zero
64 destCoef /= resultAlpha;
65 sourceCoef /= resultAlpha;
66 aDest->red = sourceCoef * aSource.red + destCoef * aDest->red;
67 aDest->green = sourceCoef * aSource.green + destCoef * aDest->green;
68 aDest->blue = sourceCoef * aSource.blue + destCoef * aDest->blue;
69 aDest->alpha = resultAlpha;
70 }
71 }
72
GetLightAndDarkness(const GdkRGBA & aColor,double * aLightness,double * aDarkness)73 static void GetLightAndDarkness(const GdkRGBA& aColor, double* aLightness,
74 double* aDarkness) {
75 double sum = aColor.red + aColor.green + aColor.blue;
76 *aLightness = sum * aColor.alpha;
77 *aDarkness = (3.0 - sum) * aColor.alpha;
78 }
79
GetGradientColors(const GValue * aValue,GdkRGBA * aLightColor,GdkRGBA * aDarkColor)80 static bool GetGradientColors(const GValue* aValue, GdkRGBA* aLightColor,
81 GdkRGBA* aDarkColor) {
82 if (!G_TYPE_CHECK_VALUE_TYPE(aValue, CAIRO_GOBJECT_TYPE_PATTERN))
83 return false;
84
85 auto pattern = static_cast<cairo_pattern_t*>(g_value_get_boxed(aValue));
86 if (!pattern) return false;
87
88 // Just picking the lightest and darkest colors as simple samples rather
89 // than trying to blend, which could get messy if there are many stops.
90 if (CAIRO_STATUS_SUCCESS !=
91 cairo_pattern_get_color_stop_rgba(pattern, 0, nullptr, &aDarkColor->red,
92 &aDarkColor->green, &aDarkColor->blue,
93 &aDarkColor->alpha))
94 return false;
95
96 double maxLightness, maxDarkness;
97 GetLightAndDarkness(*aDarkColor, &maxLightness, &maxDarkness);
98 *aLightColor = *aDarkColor;
99
100 GdkRGBA stop;
101 for (int index = 1;
102 CAIRO_STATUS_SUCCESS ==
103 cairo_pattern_get_color_stop_rgba(pattern, index, nullptr, &stop.red,
104 &stop.green, &stop.blue, &stop.alpha);
105 ++index) {
106 double lightness, darkness;
107 GetLightAndDarkness(stop, &lightness, &darkness);
108 if (lightness > maxLightness) {
109 maxLightness = lightness;
110 *aLightColor = stop;
111 }
112 if (darkness > maxDarkness) {
113 maxDarkness = darkness;
114 *aDarkColor = stop;
115 }
116 }
117
118 return true;
119 }
120
GetUnicoBorderGradientColors(GtkStyleContext * aContext,GdkRGBA * aLightColor,GdkRGBA * aDarkColor)121 static bool GetUnicoBorderGradientColors(GtkStyleContext* aContext,
122 GdkRGBA* aLightColor,
123 GdkRGBA* aDarkColor) {
124 // Ubuntu 12.04 has GTK engine Unico-1.0.2, which overrides render_frame,
125 // providing its own border code. Ubuntu 14.04 has
126 // Unico-1.0.3+14.04.20140109, which does not override render_frame, and
127 // so does not need special attention. The earlier Unico can be detected
128 // by the -unico-border-gradient style property it registers.
129 // gtk_style_properties_lookup_property() is checked first to avoid the
130 // warning from gtk_style_context_get_property() when the property does
131 // not exist. (gtk_render_frame() of GTK+ 3.16 no longer uses the
132 // engine.)
133 const char* propertyName = "-unico-border-gradient";
134 if (!gtk_style_properties_lookup_property(propertyName, nullptr, nullptr))
135 return false;
136
137 // -unico-border-gradient is used only when the CSS node's engine is Unico.
138 GtkThemingEngine* engine;
139 GtkStateFlags state = gtk_style_context_get_state(aContext);
140 gtk_style_context_get(aContext, state, "engine", &engine, nullptr);
141 if (strcmp(g_type_name(G_TYPE_FROM_INSTANCE(engine)), "UnicoEngine") != 0)
142 return false;
143
144 // draw_border() of Unico engine uses -unico-border-gradient
145 // in preference to border-color.
146 GValue value = G_VALUE_INIT;
147 gtk_style_context_get_property(aContext, propertyName, state, &value);
148
149 bool result = GetGradientColors(&value, aLightColor, aDarkColor);
150
151 g_value_unset(&value);
152 return result;
153 }
154
155 // Sets |aLightColor| and |aDarkColor| to colors from |aContext|. Returns
156 // true if |aContext| uses these colors to render a visible border.
157 // If returning false, then the colors returned are a fallback from the
158 // border-color value even though |aContext| does not use these colors to
159 // render a border.
GetBorderColors(GtkStyleContext * aContext,GdkRGBA * aLightColor,GdkRGBA * aDarkColor)160 static bool GetBorderColors(GtkStyleContext* aContext, GdkRGBA* aLightColor,
161 GdkRGBA* aDarkColor) {
162 // Determine whether the border on this style context is visible.
163 GtkStateFlags state = gtk_style_context_get_state(aContext);
164 GtkBorderStyle borderStyle;
165 gtk_style_context_get(aContext, state, GTK_STYLE_PROPERTY_BORDER_STYLE,
166 &borderStyle, nullptr);
167 bool visible = borderStyle != GTK_BORDER_STYLE_NONE &&
168 borderStyle != GTK_BORDER_STYLE_HIDDEN;
169 if (visible) {
170 // GTK has an initial value of zero for border-widths, and so themes
171 // need to explicitly set border-widths to make borders visible.
172 GtkBorder border;
173 gtk_style_context_get_border(aContext, GTK_STATE_FLAG_NORMAL, &border);
174 visible = border.top != 0 || border.right != 0 || border.bottom != 0 ||
175 border.left != 0;
176 }
177
178 if (visible &&
179 GetUnicoBorderGradientColors(aContext, aLightColor, aDarkColor))
180 return true;
181
182 // The initial value for the border-color is the foreground color, and so
183 // this will usually return a color distinct from the background even if
184 // there is no visible border detected.
185 gtk_style_context_get_border_color(aContext, state, aDarkColor);
186 // TODO GTK3 - update aLightColor
187 // for GTK_BORDER_STYLE_INSET/OUTSET/GROVE/RIDGE border styles.
188 // https://bugzilla.mozilla.org/show_bug.cgi?id=978172#c25
189 *aLightColor = *aDarkColor;
190 return visible;
191 }
192
GetBorderColors(GtkStyleContext * aContext,nscolor * aLightColor,nscolor * aDarkColor)193 static bool GetBorderColors(GtkStyleContext* aContext, nscolor* aLightColor,
194 nscolor* aDarkColor) {
195 GdkRGBA lightColor, darkColor;
196 bool ret = GetBorderColors(aContext, &lightColor, &darkColor);
197 *aLightColor = GDK_RGBA_TO_NS_RGBA(lightColor);
198 *aDarkColor = GDK_RGBA_TO_NS_RGBA(darkColor);
199 return ret;
200 }
201
NativeInit()202 void nsLookAndFeel::NativeInit() { EnsureInit(); }
203
RefreshImpl()204 void nsLookAndFeel::RefreshImpl() {
205 nsXPLookAndFeel::RefreshImpl();
206 moz_gtk_refresh();
207
208 mDefaultFontCached = false;
209 mButtonFontCached = false;
210 mFieldFontCached = false;
211 mMenuFontCached = false;
212
213 mInitialized = false;
214 }
215
NativeGetColor(ColorID aID,nscolor & aColor)216 nsresult nsLookAndFeel::NativeGetColor(ColorID aID, nscolor& aColor) {
217 EnsureInit();
218
219 nsresult res = NS_OK;
220
221 switch (aID) {
222 // These colors don't seem to be used for anything anymore in Mozilla
223 // (except here at least TextSelectBackground and TextSelectForeground)
224 // The CSS2 colors below are used.
225 case eColorID_WindowBackground:
226 case eColorID_WidgetBackground:
227 case eColorID_TextBackground:
228 case eColorID_activecaption: // active window caption background
229 case eColorID_appworkspace: // MDI background color
230 case eColorID_background: // desktop background
231 case eColorID_window:
232 case eColorID_windowframe:
233 case eColorID__moz_dialog:
234 case eColorID__moz_combobox:
235 aColor = mMozWindowBackground;
236 break;
237 case eColorID_WindowForeground:
238 case eColorID_WidgetForeground:
239 case eColorID_TextForeground:
240 case eColorID_captiontext: // text in active window caption, size box, and
241 // scrollbar arrow box (!)
242 case eColorID_windowtext:
243 case eColorID__moz_dialogtext:
244 aColor = mMozWindowText;
245 break;
246 case eColorID_WidgetSelectBackground:
247 case eColorID_TextSelectBackground:
248 case eColorID_IMESelectedRawTextBackground:
249 case eColorID_IMESelectedConvertedTextBackground:
250 case eColorID__moz_dragtargetzone:
251 case eColorID__moz_cellhighlight:
252 case eColorID__moz_html_cellhighlight:
253 case eColorID_highlight: // preference selected item,
254 aColor = mTextSelectedBackground;
255 break;
256 case eColorID_WidgetSelectForeground:
257 case eColorID_TextSelectForeground:
258 case eColorID_IMESelectedRawTextForeground:
259 case eColorID_IMESelectedConvertedTextForeground:
260 case eColorID_highlighttext:
261 case eColorID__moz_cellhighlighttext:
262 case eColorID__moz_html_cellhighlighttext:
263 aColor = mTextSelectedText;
264 break;
265 case eColorID_Widget3DHighlight:
266 aColor = NS_RGB(0xa0, 0xa0, 0xa0);
267 break;
268 case eColorID_Widget3DShadow:
269 aColor = NS_RGB(0x40, 0x40, 0x40);
270 break;
271 case eColorID_IMERawInputBackground:
272 case eColorID_IMEConvertedTextBackground:
273 aColor = NS_TRANSPARENT;
274 break;
275 case eColorID_IMERawInputForeground:
276 case eColorID_IMEConvertedTextForeground:
277 aColor = NS_SAME_AS_FOREGROUND_COLOR;
278 break;
279 case eColorID_IMERawInputUnderline:
280 case eColorID_IMEConvertedTextUnderline:
281 aColor = NS_SAME_AS_FOREGROUND_COLOR;
282 break;
283 case eColorID_IMESelectedRawTextUnderline:
284 case eColorID_IMESelectedConvertedTextUnderline:
285 aColor = NS_TRANSPARENT;
286 break;
287 case eColorID_SpellCheckerUnderline:
288 aColor = NS_RGB(0xff, 0, 0);
289 break;
290
291 // css2 http://www.w3.org/TR/REC-CSS2/ui.html#system-colors
292 case eColorID_activeborder:
293 // active window border
294 aColor = mMozWindowActiveBorder;
295 break;
296 case eColorID_inactiveborder:
297 // inactive window border
298 aColor = mMozWindowInactiveBorder;
299 break;
300 case eColorID_graytext: // disabled text in windows, menus, etc.
301 case eColorID_inactivecaptiontext: // text in inactive window caption
302 aColor = mMenuTextInactive;
303 break;
304 case eColorID_inactivecaption:
305 // inactive window caption
306 aColor = mMozWindowInactiveCaption;
307 break;
308 case eColorID_infobackground:
309 // tooltip background color
310 aColor = mInfoBackground;
311 break;
312 case eColorID_infotext:
313 // tooltip text color
314 aColor = mInfoText;
315 break;
316 case eColorID_menu:
317 // menu background
318 aColor = mMenuBackground;
319 break;
320 case eColorID_menutext:
321 // menu text
322 aColor = mMenuText;
323 break;
324 case eColorID_scrollbar:
325 // scrollbar gray area
326 aColor = mMozScrollbar;
327 break;
328
329 case eColorID_threedlightshadow:
330 // 3-D highlighted inner edge color
331 // always same as background in GTK code
332 case eColorID_threedface:
333 case eColorID_buttonface:
334 // 3-D face color
335 aColor = mMozWindowBackground;
336 break;
337
338 case eColorID_buttontext:
339 // text on push buttons
340 aColor = mButtonText;
341 break;
342
343 case eColorID_buttonhighlight:
344 // 3-D highlighted edge color
345 case eColorID_threedhighlight:
346 // 3-D highlighted outer edge color
347 aColor = mFrameOuterLightBorder;
348 break;
349
350 case eColorID_buttonshadow:
351 // 3-D shadow edge color
352 case eColorID_threedshadow:
353 // 3-D shadow inner edge color
354 aColor = mFrameInnerDarkBorder;
355 break;
356
357 case eColorID_threeddarkshadow:
358 // Hardcode to black
359 aColor = NS_RGB(0x00, 0x00, 0x00);
360 break;
361
362 case eColorID__moz_eventreerow:
363 case eColorID__moz_field:
364 aColor = mMozFieldBackground;
365 break;
366 case eColorID__moz_fieldtext:
367 aColor = mMozFieldText;
368 break;
369 case eColorID__moz_buttondefault:
370 // default button border color
371 aColor = mButtonDefault;
372 break;
373 case eColorID__moz_buttonhoverface:
374 aColor = mButtonHoverFace;
375 break;
376 case eColorID__moz_buttonhovertext:
377 aColor = mButtonHoverText;
378 break;
379 case eColorID__moz_menuhover:
380 aColor = mMenuHover;
381 break;
382 case eColorID__moz_menuhovertext:
383 aColor = mMenuHoverText;
384 break;
385 case eColorID__moz_oddtreerow:
386 aColor = mOddCellBackground;
387 break;
388 case eColorID__moz_nativehyperlinktext:
389 aColor = mNativeHyperLinkText;
390 break;
391 case eColorID__moz_comboboxtext:
392 aColor = mComboBoxText;
393 break;
394 case eColorID__moz_menubartext:
395 aColor = mMenuBarText;
396 break;
397 case eColorID__moz_menubarhovertext:
398 aColor = mMenuBarHoverText;
399 break;
400 case eColorID__moz_gtk_info_bar_text:
401 aColor = mInfoBarText;
402 break;
403 default:
404 /* default color is BLACK */
405 aColor = 0;
406 res = NS_ERROR_FAILURE;
407 break;
408 }
409
410 return res;
411 }
412
CheckWidgetStyle(GtkWidget * aWidget,const char * aStyle,int32_t aResult)413 static int32_t CheckWidgetStyle(GtkWidget* aWidget, const char* aStyle,
414 int32_t aResult) {
415 gboolean value = FALSE;
416 gtk_widget_style_get(aWidget, aStyle, &value, nullptr);
417 return value ? aResult : 0;
418 }
419
ConvertGTKStepperStyleToMozillaScrollArrowStyle(GtkWidget * aWidget)420 static int32_t ConvertGTKStepperStyleToMozillaScrollArrowStyle(
421 GtkWidget* aWidget) {
422 if (!aWidget) return mozilla::LookAndFeel::eScrollArrowStyle_Single;
423
424 return CheckWidgetStyle(aWidget, "has-backward-stepper",
425 mozilla::LookAndFeel::eScrollArrow_StartBackward) |
426 CheckWidgetStyle(aWidget, "has-forward-stepper",
427 mozilla::LookAndFeel::eScrollArrow_EndForward) |
428 CheckWidgetStyle(aWidget, "has-secondary-backward-stepper",
429 mozilla::LookAndFeel::eScrollArrow_EndBackward) |
430 CheckWidgetStyle(aWidget, "has-secondary-forward-stepper",
431 mozilla::LookAndFeel::eScrollArrow_StartForward);
432 }
433
GetIntImpl(IntID aID,int32_t & aResult)434 nsresult nsLookAndFeel::GetIntImpl(IntID aID, int32_t& aResult) {
435 nsresult res = NS_OK;
436
437 // Set these before they can get overrided in the nsXPLookAndFeel.
438 switch (aID) {
439 case eIntID_ScrollButtonLeftMouseButtonAction:
440 aResult = 0;
441 return NS_OK;
442 case eIntID_ScrollButtonMiddleMouseButtonAction:
443 aResult = 1;
444 return NS_OK;
445 case eIntID_ScrollButtonRightMouseButtonAction:
446 aResult = 2;
447 return NS_OK;
448 default:
449 break;
450 }
451
452 res = nsXPLookAndFeel::GetIntImpl(aID, aResult);
453 if (NS_SUCCEEDED(res)) return res;
454 res = NS_OK;
455
456 // We use delayed initialization by EnsureInit() here
457 // to make sure mozilla::Preferences is available (Bug 115807).
458 // eIntID_UseAccessibilityTheme is requested before user preferences
459 // are read, and so EnsureInit(), which depends on preference values,
460 // is deliberately delayed until required.
461 switch (aID) {
462 case eIntID_CaretBlinkTime: {
463 GtkSettings* settings;
464 gint blink_time;
465 gboolean blink;
466
467 settings = gtk_settings_get_default();
468 g_object_get(settings, "gtk-cursor-blink-time", &blink_time,
469 "gtk-cursor-blink", &blink, nullptr);
470
471 if (blink)
472 aResult = (int32_t)blink_time;
473 else
474 aResult = 0;
475 break;
476 }
477 case eIntID_CaretWidth:
478 aResult = 1;
479 break;
480 case eIntID_ShowCaretDuringSelection:
481 aResult = 0;
482 break;
483 case eIntID_SelectTextfieldsOnKeyFocus: {
484 GtkWidget* entry;
485 GtkSettings* settings;
486 gboolean select_on_focus;
487
488 entry = gtk_entry_new();
489 g_object_ref_sink(entry);
490 settings = gtk_widget_get_settings(entry);
491 g_object_get(settings, "gtk-entry-select-on-focus", &select_on_focus,
492 nullptr);
493
494 if (select_on_focus)
495 aResult = 1;
496 else
497 aResult = 0;
498
499 gtk_widget_destroy(entry);
500 g_object_unref(entry);
501 } break;
502 case eIntID_ScrollToClick: {
503 GtkSettings* settings;
504 gboolean warps_slider = FALSE;
505
506 settings = gtk_settings_get_default();
507 if (g_object_class_find_property(G_OBJECT_GET_CLASS(settings),
508 "gtk-primary-button-warps-slider")) {
509 g_object_get(settings, "gtk-primary-button-warps-slider", &warps_slider,
510 nullptr);
511 }
512
513 if (warps_slider)
514 aResult = 1;
515 else
516 aResult = 0;
517 } break;
518 case eIntID_SubmenuDelay: {
519 GtkSettings* settings;
520 gint delay;
521
522 settings = gtk_settings_get_default();
523 g_object_get(settings, "gtk-menu-popup-delay", &delay, nullptr);
524 aResult = (int32_t)delay;
525 break;
526 }
527 case eIntID_TooltipDelay: {
528 aResult = 500;
529 break;
530 }
531 case eIntID_MenusCanOverlapOSBar:
532 // we want XUL popups to be able to overlap the task bar.
533 aResult = 1;
534 break;
535 case eIntID_SkipNavigatingDisabledMenuItem:
536 aResult = 1;
537 break;
538 case eIntID_DragThresholdX:
539 case eIntID_DragThresholdY: {
540 GtkWidget* box = gtk_hbox_new(FALSE, 5);
541 gint threshold = 0;
542 g_object_get(gtk_widget_get_settings(box), "gtk-dnd-drag-threshold",
543 &threshold, nullptr);
544 g_object_ref_sink(box);
545
546 aResult = threshold;
547 } break;
548 case eIntID_ScrollArrowStyle: {
549 GtkWidget* scrollbar = GetWidget(MOZ_GTK_SCROLLBAR_HORIZONTAL);
550 aResult = ConvertGTKStepperStyleToMozillaScrollArrowStyle(scrollbar);
551 break;
552 }
553 case eIntID_ScrollSliderStyle:
554 aResult = eScrollThumbStyle_Proportional;
555 break;
556 case eIntID_TreeOpenDelay:
557 aResult = 1000;
558 break;
559 case eIntID_TreeCloseDelay:
560 aResult = 1000;
561 break;
562 case eIntID_TreeLazyScrollDelay:
563 aResult = 150;
564 break;
565 case eIntID_TreeScrollDelay:
566 aResult = 100;
567 break;
568 case eIntID_TreeScrollLinesMax:
569 aResult = 3;
570 break;
571 case eIntID_DWMCompositor:
572 case eIntID_WindowsClassic:
573 case eIntID_WindowsDefaultTheme:
574 case eIntID_WindowsThemeIdentifier:
575 case eIntID_OperatingSystemVersionIdentifier:
576 aResult = 0;
577 res = NS_ERROR_NOT_IMPLEMENTED;
578 break;
579 case eIntID_TouchEnabled:
580 aResult = mozilla::widget::WidgetUtils::IsTouchDeviceSupportPresent();
581 break;
582 case eIntID_MacGraphiteTheme:
583 aResult = 0;
584 res = NS_ERROR_NOT_IMPLEMENTED;
585 break;
586 case eIntID_AlertNotificationOrigin:
587 aResult = NS_ALERT_TOP;
588 break;
589 case eIntID_IMERawInputUnderlineStyle:
590 case eIntID_IMEConvertedTextUnderlineStyle:
591 aResult = NS_STYLE_TEXT_DECORATION_STYLE_SOLID;
592 break;
593 case eIntID_IMESelectedRawTextUnderlineStyle:
594 case eIntID_IMESelectedConvertedTextUnderline:
595 aResult = NS_STYLE_TEXT_DECORATION_STYLE_NONE;
596 break;
597 case eIntID_SpellCheckerUnderlineStyle:
598 aResult = NS_STYLE_TEXT_DECORATION_STYLE_WAVY;
599 break;
600 case eIntID_MenuBarDrag:
601 EnsureInit();
602 aResult = mMenuSupportsDrag;
603 break;
604 case eIntID_ScrollbarButtonAutoRepeatBehavior:
605 aResult = 1;
606 break;
607 case eIntID_SwipeAnimationEnabled:
608 aResult = 0;
609 break;
610 case eIntID_ContextMenuOffsetVertical:
611 case eIntID_ContextMenuOffsetHorizontal:
612 aResult = 2;
613 break;
614 case eIntID_GTKCSDAvailable:
615 EnsureInit();
616 aResult = mCSDAvailable;
617 break;
618 case eIntID_GTKCSDMaximizeButton:
619 EnsureInit();
620 aResult = mCSDMaximizeButton;
621 break;
622 case eIntID_GTKCSDMinimizeButton:
623 EnsureInit();
624 aResult = mCSDMinimizeButton;
625 break;
626 case eIntID_GTKCSDCloseButton:
627 EnsureInit();
628 aResult = mCSDCloseButton;
629 break;
630 default:
631 aResult = 0;
632 res = NS_ERROR_FAILURE;
633 }
634
635 return res;
636 }
637
GetFloatImpl(FloatID aID,float & aResult)638 nsresult nsLookAndFeel::GetFloatImpl(FloatID aID, float& aResult) {
639 nsresult res = NS_OK;
640 res = nsXPLookAndFeel::GetFloatImpl(aID, aResult);
641 if (NS_SUCCEEDED(res)) return res;
642 res = NS_OK;
643
644 switch (aID) {
645 case eFloatID_IMEUnderlineRelativeSize:
646 aResult = 1.0f;
647 break;
648 case eFloatID_SpellCheckerUnderlineRelativeSize:
649 aResult = 1.0f;
650 break;
651 case eFloatID_CaretAspectRatio:
652 EnsureInit();
653 aResult = mCaretRatio;
654 break;
655 default:
656 aResult = -1.0;
657 res = NS_ERROR_FAILURE;
658 }
659 return res;
660 }
661
GetSystemFontInfo(GtkStyleContext * aStyle,nsString * aFontName,gfxFontStyle * aFontStyle)662 static void GetSystemFontInfo(GtkStyleContext* aStyle, nsString* aFontName,
663 gfxFontStyle* aFontStyle) {
664 aFontStyle->style = NS_FONT_STYLE_NORMAL;
665
666 // As in
667 // https://git.gnome.org/browse/gtk+/tree/gtk/gtkwidget.c?h=3.22.19#n10333
668 PangoFontDescription* desc;
669 gtk_style_context_get(aStyle, gtk_style_context_get_state(aStyle), "font",
670 &desc, nullptr);
671
672 aFontStyle->systemFont = true;
673
674 NS_NAMED_LITERAL_STRING(quote, "\"");
675 NS_ConvertUTF8toUTF16 family(pango_font_description_get_family(desc));
676 *aFontName = quote + family + quote;
677
678 aFontStyle->weight = pango_font_description_get_weight(desc);
679
680 // FIXME: Set aFontStyle->stretch correctly!
681 aFontStyle->stretch = NS_FONT_STRETCH_NORMAL;
682
683 float size = float(pango_font_description_get_size(desc)) / PANGO_SCALE;
684
685 // |size| is now either pixels or pango-points (not Mozilla-points!)
686
687 if (!pango_font_description_get_size_is_absolute(desc)) {
688 // |size| is in pango-points, so convert to pixels.
689 size *= float(gfxPlatformGtk::GetFontScaleDPI()) / POINTS_PER_INCH_FLOAT;
690 }
691 // |size| is now pixels but not scaled for the hidpi displays,
692 // this needs to be done in GetFontImpl where the aDevPixPerCSSPixel
693 // parameter is provided.
694
695 aFontStyle->size = size;
696
697 pango_font_description_free(desc);
698 }
699
GetFontImpl(FontID aID,nsString & aFontName,gfxFontStyle & aFontStyle,float aDevPixPerCSSPixel)700 bool nsLookAndFeel::GetFontImpl(FontID aID, nsString& aFontName,
701 gfxFontStyle& aFontStyle,
702 float aDevPixPerCSSPixel) {
703 switch (aID) {
704 case eFont_Menu: // css2
705 case eFont_PullDownMenu: // css3
706 aFontName = mMenuFontName;
707 aFontStyle = mMenuFontStyle;
708 break;
709
710 case eFont_Field: // css3
711 case eFont_List: // css3
712 aFontName = mFieldFontName;
713 aFontStyle = mFieldFontStyle;
714 break;
715
716 case eFont_Button: // css3
717 aFontName = mButtonFontName;
718 aFontStyle = mButtonFontStyle;
719 break;
720
721 case eFont_Caption: // css2
722 case eFont_Icon: // css2
723 case eFont_MessageBox: // css2
724 case eFont_SmallCaption: // css2
725 case eFont_StatusBar: // css2
726 case eFont_Window: // css3
727 case eFont_Document: // css3
728 case eFont_Workspace: // css3
729 case eFont_Desktop: // css3
730 case eFont_Info: // css3
731 case eFont_Dialog: // css3
732 case eFont_Tooltips: // moz
733 case eFont_Widget: // moz
734 default:
735 aFontName = mDefaultFontName;
736 aFontStyle = mDefaultFontStyle;
737 break;
738 }
739 // Scale the font for the current monitor
740 double scaleFactor = nsIWidget::DefaultScaleOverride();
741 if (scaleFactor > 0) {
742 aFontStyle.size *=
743 mozilla::widget::ScreenHelperGTK::GetGTKMonitorScaleFactor();
744 } else {
745 // Remove effect of font scale because it has been already applied in
746 // GetSystemFontInfo
747 aFontStyle.size *=
748 aDevPixPerCSSPixel / gfxPlatformGtk::GetFontScaleFactor();
749 }
750 return true;
751 }
752
EnsureInit()753 void nsLookAndFeel::EnsureInit() {
754 GdkColor colorValue;
755 GdkColor* colorValuePtr;
756
757 if (mInitialized) return;
758 mInitialized = true;
759
760 // gtk does non threadsafe refcounting
761 MOZ_ASSERT(NS_IsMainThread());
762
763 GdkRGBA color;
764 GtkStyleContext* style;
765
766 // Gtk manages a screen's CSS in the settings object so we
767 // ask Gtk to create it explicitly. Otherwise we may end up
768 // with wrong color theme, see Bug 972382
769 GtkSettings* settings = gtk_settings_get_for_screen(gdk_screen_get_default());
770
771 // Dark themes interacts poorly with widget styling (see bug 1216658).
772 // We disable dark themes by default for all processes (chrome, web content)
773 // but allow user to overide it by prefs.
774 const gchar* dark_setting = "gtk-application-prefer-dark-theme";
775 gboolean darkThemeDefault;
776 g_object_get(settings, dark_setting, &darkThemeDefault, nullptr);
777
778 // To avoid triggering reload of theme settings unnecessarily, only set the
779 // setting when necessary.
780 if (darkThemeDefault) {
781 bool allowDarkTheme;
782 if (XRE_IsContentProcess()) {
783 allowDarkTheme = mozilla::Preferences::GetBool(
784 "widget.content.allow-gtk-dark-theme", false);
785 } else {
786 allowDarkTheme = (PR_GetEnv("MOZ_ALLOW_GTK_DARK_THEME") != nullptr) ||
787 mozilla::Preferences::GetBool(
788 "widget.chrome.allow-gtk-dark-theme", false);
789 }
790 if (!allowDarkTheme) {
791 g_object_set(settings, dark_setting, FALSE, nullptr);
792 }
793 }
794
795 // Allow content Gtk theme override by pref, it's useful when styled Gtk+
796 // widgets break web content.
797 if (XRE_IsContentProcess()) {
798 nsAutoCString contentThemeName;
799 mozilla::Preferences::GetCString("widget.content.gtk-theme-override",
800 contentThemeName);
801 if (!contentThemeName.IsEmpty()) {
802 g_object_set(settings, "gtk-theme-name", contentThemeName.get(), nullptr);
803 }
804 }
805
806 // The label is not added to a parent widget, but shared for constructing
807 // different style contexts. The node hierarchy is constructed only on
808 // the label style context.
809 GtkWidget* labelWidget = gtk_label_new("M");
810 g_object_ref_sink(labelWidget);
811
812 // Scrollbar colors
813 style = GetStyleContext(MOZ_GTK_SCROLLBAR_TROUGH_VERTICAL);
814 gtk_style_context_get_background_color(style, GTK_STATE_FLAG_NORMAL, &color);
815 mMozScrollbar = GDK_RGBA_TO_NS_RGBA(color);
816
817 // Window colors
818 style = GetStyleContext(MOZ_GTK_WINDOW);
819 gtk_style_context_get_background_color(style, GTK_STATE_FLAG_NORMAL, &color);
820 mMozWindowBackground = GDK_RGBA_TO_NS_RGBA(color);
821 gtk_style_context_get_color(style, GTK_STATE_FLAG_NORMAL, &color);
822 mMozWindowText = GDK_RGBA_TO_NS_RGBA(color);
823 gtk_style_context_get_border_color(style, GTK_STATE_FLAG_NORMAL, &color);
824 mMozWindowActiveBorder = GDK_RGBA_TO_NS_RGBA(color);
825 gtk_style_context_get_border_color(style, GTK_STATE_FLAG_INSENSITIVE, &color);
826 mMozWindowInactiveBorder = GDK_RGBA_TO_NS_RGBA(color);
827 gtk_style_context_get_background_color(style, GTK_STATE_FLAG_INSENSITIVE,
828 &color);
829 mMozWindowInactiveCaption = GDK_RGBA_TO_NS_RGBA(color);
830
831 style = GetStyleContext(MOZ_GTK_WINDOW_CONTAINER);
832 {
833 GtkStyleContext* labelStyle = CreateStyleForWidget(labelWidget, style);
834 GetSystemFontInfo(labelStyle, &mDefaultFontName, &mDefaultFontStyle);
835 g_object_unref(labelStyle);
836 }
837
838 // tooltip foreground and background
839 style = GetStyleContext(MOZ_GTK_TOOLTIP);
840 gtk_style_context_get_background_color(style, GTK_STATE_FLAG_NORMAL, &color);
841 mInfoBackground = GDK_RGBA_TO_NS_RGBA(color);
842
843 style = GetStyleContext(MOZ_GTK_TOOLTIP_BOX_LABEL);
844 gtk_style_context_get_color(style, GTK_STATE_FLAG_NORMAL, &color);
845 mInfoText = GDK_RGBA_TO_NS_RGBA(color);
846
847 style = GetStyleContext(MOZ_GTK_MENUITEM);
848 {
849 GtkStyleContext* accelStyle =
850 CreateStyleForWidget(gtk_accel_label_new("M"), style);
851
852 GetSystemFontInfo(accelStyle, &mMenuFontName, &mMenuFontStyle);
853
854 gtk_style_context_get_color(accelStyle, GTK_STATE_FLAG_NORMAL, &color);
855 mMenuText = GDK_RGBA_TO_NS_RGBA(color);
856 gtk_style_context_get_color(accelStyle, GTK_STATE_FLAG_INSENSITIVE, &color);
857 mMenuTextInactive = GDK_RGBA_TO_NS_RGBA(color);
858 g_object_unref(accelStyle);
859 }
860
861 style = GetStyleContext(MOZ_GTK_MENUPOPUP);
862 gtk_style_context_get_background_color(style, GTK_STATE_FLAG_NORMAL, &color);
863 mMenuBackground = GDK_RGBA_TO_NS_RGBA(color);
864
865 style = GetStyleContext(MOZ_GTK_MENUITEM);
866 gtk_style_context_get_background_color(style, GTK_STATE_FLAG_PRELIGHT,
867 &color);
868 mMenuHover = GDK_RGBA_TO_NS_RGBA(color);
869 gtk_style_context_get_color(style, GTK_STATE_FLAG_PRELIGHT, &color);
870 mMenuHoverText = GDK_RGBA_TO_NS_RGBA(color);
871
872 GtkWidget* parent = gtk_fixed_new();
873 GtkWidget* window = gtk_window_new(GTK_WINDOW_POPUP);
874 GtkWidget* treeView = gtk_tree_view_new();
875 GtkWidget* linkButton = gtk_link_button_new("http://example.com/");
876 GtkWidget* menuBar = gtk_menu_bar_new();
877 GtkWidget* menuBarItem = gtk_menu_item_new();
878 GtkWidget* entry = gtk_entry_new();
879 GtkWidget* textView = gtk_text_view_new();
880
881 gtk_container_add(GTK_CONTAINER(parent), treeView);
882 gtk_container_add(GTK_CONTAINER(parent), linkButton);
883 gtk_container_add(GTK_CONTAINER(parent), menuBar);
884 gtk_menu_shell_append(GTK_MENU_SHELL(menuBar), menuBarItem);
885 gtk_container_add(GTK_CONTAINER(window), parent);
886 gtk_container_add(GTK_CONTAINER(parent), entry);
887 gtk_container_add(GTK_CONTAINER(parent), textView);
888
889 // Text colors
890 GdkRGBA bgColor;
891 // If the text window background is translucent, then the background of
892 // the textview root node is visible.
893 style = GetStyleContext(MOZ_GTK_TEXT_VIEW);
894 gtk_style_context_get_background_color(style, GTK_STATE_FLAG_NORMAL,
895 &bgColor);
896
897 style = GetStyleContext(MOZ_GTK_TEXT_VIEW_TEXT);
898 gtk_style_context_get_background_color(style, GTK_STATE_FLAG_NORMAL, &color);
899 ApplyColorOver(color, &bgColor);
900 mMozFieldBackground = GDK_RGBA_TO_NS_RGBA(bgColor);
901 gtk_style_context_get_color(style, GTK_STATE_FLAG_NORMAL, &color);
902 mMozFieldText = GDK_RGBA_TO_NS_RGBA(color);
903
904 // Selected text and background
905 gtk_style_context_get_background_color(
906 style,
907 static_cast<GtkStateFlags>(GTK_STATE_FLAG_FOCUSED |
908 GTK_STATE_FLAG_SELECTED),
909 &color);
910 mTextSelectedBackground = GDK_RGBA_TO_NS_RGBA(color);
911 gtk_style_context_get_color(
912 style,
913 static_cast<GtkStateFlags>(GTK_STATE_FLAG_FOCUSED |
914 GTK_STATE_FLAG_SELECTED),
915 &color);
916 mTextSelectedText = GDK_RGBA_TO_NS_RGBA(color);
917
918 // Button text color
919 style = GetStyleContext(MOZ_GTK_BUTTON);
920 {
921 GtkStyleContext* labelStyle = CreateStyleForWidget(labelWidget, style);
922
923 GetSystemFontInfo(labelStyle, &mButtonFontName, &mButtonFontStyle);
924
925 gtk_style_context_get_border_color(style, GTK_STATE_FLAG_NORMAL, &color);
926 mButtonDefault = GDK_RGBA_TO_NS_RGBA(color);
927 gtk_style_context_get_color(labelStyle, GTK_STATE_FLAG_NORMAL, &color);
928 mButtonText = GDK_RGBA_TO_NS_RGBA(color);
929 gtk_style_context_get_color(labelStyle, GTK_STATE_FLAG_PRELIGHT, &color);
930 mButtonHoverText = GDK_RGBA_TO_NS_RGBA(color);
931 gtk_style_context_get_background_color(style, GTK_STATE_FLAG_PRELIGHT,
932 &color);
933 mButtonHoverFace = GDK_RGBA_TO_NS_RGBA(color);
934 g_object_unref(labelStyle);
935 }
936
937 // Combobox text color
938 style = GetStyleContext(MOZ_GTK_COMBOBOX_ENTRY_TEXTAREA);
939 gtk_style_context_get_color(style, GTK_STATE_FLAG_NORMAL, &color);
940 mComboBoxText = GDK_RGBA_TO_NS_RGBA(color);
941
942 // Menubar text and hover text colors
943 style = GetStyleContext(MOZ_GTK_MENUBARITEM);
944 gtk_style_context_get_color(style, GTK_STATE_FLAG_NORMAL, &color);
945 mMenuBarText = GDK_RGBA_TO_NS_RGBA(color);
946 gtk_style_context_get_color(style, GTK_STATE_FLAG_PRELIGHT, &color);
947 mMenuBarHoverText = GDK_RGBA_TO_NS_RGBA(color);
948
949 // GTK's guide to fancy odd row background colors:
950 // 1) Check if a theme explicitly defines an odd row color
951 // 2) If not, check if it defines an even row color, and darken it
952 // slightly by a hardcoded value (gtkstyle.c)
953 // 3) If neither are defined, take the base background color and
954 // darken that by a hardcoded value
955 style = GetStyleContext(MOZ_GTK_TREEVIEW);
956
957 // Get odd row background color
958 gtk_style_context_save(style);
959 gtk_style_context_add_region(style, GTK_STYLE_REGION_ROW, GTK_REGION_ODD);
960 gtk_style_context_get_background_color(style, GTK_STATE_FLAG_NORMAL, &color);
961 mOddCellBackground = GDK_RGBA_TO_NS_RGBA(color);
962 gtk_style_context_restore(style);
963
964 // GtkFrame has a "border" subnode on which Adwaita draws the border.
965 // Some themes do not draw on this node but draw a border on the widget
966 // root node, so check the root node if no border is found on the border
967 // node.
968 style = GetStyleContext(MOZ_GTK_FRAME_BORDER);
969 bool themeUsesColors =
970 GetBorderColors(style, &mFrameOuterLightBorder, &mFrameInnerDarkBorder);
971 if (!themeUsesColors) {
972 style = GetStyleContext(MOZ_GTK_FRAME);
973 GetBorderColors(style, &mFrameOuterLightBorder, &mFrameInnerDarkBorder);
974 }
975
976 // GtkInfoBar
977 // TODO - Use WidgetCache for it?
978 GtkWidget* infoBar = gtk_info_bar_new();
979 GtkWidget* infoBarContent =
980 gtk_info_bar_get_content_area(GTK_INFO_BAR(infoBar));
981 GtkWidget* infoBarLabel = gtk_label_new(nullptr);
982 gtk_container_add(GTK_CONTAINER(parent), infoBar);
983 gtk_container_add(GTK_CONTAINER(infoBarContent), infoBarLabel);
984 style = gtk_widget_get_style_context(infoBarLabel);
985 gtk_style_context_add_class(style, GTK_STYLE_CLASS_INFO);
986 gtk_style_context_get_color(style, GTK_STATE_FLAG_NORMAL, &color);
987 mInfoBarText = GDK_RGBA_TO_NS_RGBA(color);
988 // Some themes have a unified menu bar, and support window dragging on it
989 gboolean supports_menubar_drag = FALSE;
990 GParamSpec* param_spec = gtk_widget_class_find_style_property(
991 GTK_WIDGET_GET_CLASS(menuBar), "window-dragging");
992 if (param_spec) {
993 if (g_type_is_a(G_PARAM_SPEC_VALUE_TYPE(param_spec), G_TYPE_BOOLEAN)) {
994 gtk_widget_style_get(menuBar, "window-dragging", &supports_menubar_drag,
995 nullptr);
996 }
997 }
998 mMenuSupportsDrag = supports_menubar_drag;
999
1000 if (gtk_check_version(3, 12, 0) == nullptr) {
1001 // TODO: It returns wrong color for themes which
1002 // sets link color for GtkLabel only as we query
1003 // GtkLinkButton style here.
1004 style = gtk_widget_get_style_context(linkButton);
1005 gtk_style_context_get_color(style, GTK_STATE_FLAG_LINK, &color);
1006 mNativeHyperLinkText = GDK_RGBA_TO_NS_RGBA(color);
1007 } else {
1008 colorValuePtr = nullptr;
1009 gtk_widget_style_get(linkButton, "link-color", &colorValuePtr, nullptr);
1010 if (colorValuePtr) {
1011 colorValue = *colorValuePtr; // we can't pass deref pointers to
1012 // GDK_COLOR_TO_NS_RGB
1013 mNativeHyperLinkText = GDK_COLOR_TO_NS_RGB(colorValue);
1014 gdk_color_free(colorValuePtr);
1015 } else {
1016 mNativeHyperLinkText = NS_RGB(0x00, 0x00, 0xEE);
1017 }
1018 }
1019
1020 // invisible character styles
1021 guint value;
1022 g_object_get(entry, "invisible-char", &value, nullptr);
1023 mInvisibleCharacter = char16_t(value);
1024
1025 // caret styles
1026 gtk_widget_style_get(entry, "cursor-aspect-ratio", &mCaretRatio, nullptr);
1027
1028 GetSystemFontInfo(gtk_widget_get_style_context(entry), &mFieldFontName,
1029 &mFieldFontStyle);
1030
1031 gtk_widget_destroy(window);
1032 g_object_unref(labelWidget);
1033
1034 mCSDAvailable =
1035 nsWindow::GetSystemCSDSupportLevel() != nsWindow::CSD_SUPPORT_NONE;
1036
1037 mCSDCloseButton = false;
1038 mCSDMinimizeButton = false;
1039 mCSDMaximizeButton = false;
1040
1041 // We need to initialize whole CSD config explicitly because it's queried
1042 // as -moz-gtk* media features.
1043 WidgetNodeType buttonLayout[TOOLBAR_BUTTONS];
1044
1045 int activeButtons =
1046 GetGtkHeaderBarButtonLayout(buttonLayout, TOOLBAR_BUTTONS);
1047 for (int i = 0; i < activeButtons; i++) {
1048 switch (buttonLayout[i]) {
1049 case MOZ_GTK_HEADER_BAR_BUTTON_MINIMIZE:
1050 mCSDMinimizeButton = true;
1051 break;
1052 case MOZ_GTK_HEADER_BAR_BUTTON_MAXIMIZE:
1053 mCSDMaximizeButton = true;
1054 break;
1055 case MOZ_GTK_HEADER_BAR_BUTTON_CLOSE:
1056 mCSDCloseButton = true;
1057 break;
1058 default:
1059 break;
1060 }
1061 }
1062 }
1063
1064 // virtual
GetPasswordCharacterImpl()1065 char16_t nsLookAndFeel::GetPasswordCharacterImpl() {
1066 EnsureInit();
1067 return mInvisibleCharacter;
1068 }
1069
GetEchoPasswordImpl()1070 bool nsLookAndFeel::GetEchoPasswordImpl() { return false; }
1071