1/*
2 * Copyright (C) 2007 Apple Inc.
3 * Copyright (C) 2007 Alp Toker <alp@atoker.com>
4 * Copyright (C) 2008 Collabora Ltd.
5 * Copyright (C) 2009 Kenneth Rohde Christiansen
6 * Copyright (C) 2010 Igalia S.L.
7 *
8 * This library is free software; you can redistribute it and/or
9 * modify it under the terms of the GNU Library General Public
10 * License as published by the Free Software Foundation; either
11 * version 2 of the License, or (at your option) any later version.
12 *
13 * This library is distributed in the hope that it will be useful,
14 * but WITHOUT ANY WARRANTY; without even the implied warranty of
15 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
16 * Library General Public License for more details.
17 *
18 * You should have received a copy of the GNU Library General Public License
19 * along with this library; see the file COPYING.LIB. If not, write to
20 * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
21 * Boston, MA 02110-1301, USA.
22 *
23 */
24
25#include "config.h"
26#include "RenderThemeGtk.h"
27
28#include "CSSValueKeywords.h"
29#include "FileList.h"
30#include "FloatRoundedRect.h"
31#include "FontDescription.h"
32#include "GRefPtrGtk.h"
33#include "GUniquePtrGtk.h"
34#include "Gradient.h"
35#include "GraphicsContext.h"
36#include "GtkVersioning.h"
37#include "HTMLInputElement.h"
38#include "HTMLMediaElement.h"
39#include "LocalizedStrings.h"
40#include "MediaControlElements.h"
41#include "Page.h"
42#include "PaintInfo.h"
43#include "PlatformContextCairo.h"
44#include "RenderBox.h"
45#include "RenderObject.h"
46#include "RenderProgress.h"
47#include "RenderThemeWidget.h"
48#include "ScrollbarThemeGtk.h"
49#include "StringTruncator.h"
50#include "TimeRanges.h"
51#include "UserAgentScripts.h"
52#include "UserAgentStyleSheets.h"
53#include <cmath>
54#include <gdk/gdk.h>
55#include <glib.h>
56#include <gtk/gtk.h>
57#include <wtf/FileSystem.h>
58#include <wtf/glib/GRefPtr.h>
59#include <wtf/glib/GUniquePtr.h>
60#include <wtf/text/CString.h>
61#include <wtf/text/StringBuilder.h>
62
63namespace WebCore {
64
65RenderTheme& RenderTheme::singleton()
66{
67 static NeverDestroyed<RenderThemeGtk> theme;
68 return theme;
69}
70
71static double getScreenDPI()
72{
73 // FIXME: Really this should be the widget's screen.
74 GdkScreen* screen = gdk_screen_get_default();
75 if (!screen)
76 return 96; // Default to 96 DPI.
77
78 float dpi = gdk_screen_get_resolution(screen);
79 if (dpi <= 0)
80 return 96;
81 return dpi;
82}
83
84void RenderThemeGtk::updateCachedSystemFontDescription(CSSValueID, FontCascadeDescription& fontDescription) const
85{
86 GtkSettings* settings = gtk_settings_get_default();
87 if (!settings)
88 return;
89
90 // This will be a font selection string like "Sans 10" so we cannot use it as the family name.
91 GUniqueOutPtr<gchar> fontName;
92 g_object_get(settings, "gtk-font-name", &fontName.outPtr(), nullptr);
93 if (!fontName || !fontName.get()[0])
94 return;
95
96 PangoFontDescription* pangoDescription = pango_font_description_from_string(fontName.get());
97 if (!pangoDescription)
98 return;
99
100 fontDescription.setOneFamily(pango_font_description_get_family(pangoDescription));
101
102 int size = pango_font_description_get_size(pangoDescription) / PANGO_SCALE;
103 // If the size of the font is in points, we need to convert it to pixels.
104 if (!pango_font_description_get_size_is_absolute(pangoDescription))
105 size = size * (getScreenDPI() / 72.0);
106
107 fontDescription.setSpecifiedSize(size);
108 fontDescription.setIsAbsoluteSize(true);
109 fontDescription.setWeight(normalWeightValue());
110 fontDescription.setItalic(FontSelectionValue());
111 pango_font_description_free(pangoDescription);
112}
113
114#if ENABLE(DATALIST_ELEMENT)
115IntSize RenderThemeGtk::sliderTickSize() const
116{
117 // FIXME: We need to set this to the size of one tick mark.
118 return IntSize(0, 0);
119}
120
121int RenderThemeGtk::sliderTickOffsetFromTrackCenter() const
122{
123 // FIXME: We need to set this to the position of the tick marks.
124 return 0;
125}
126#endif
127
128#ifndef GTK_API_VERSION_2
129
130static void themeChangedCallback()
131{
132 Page::updateStyleForAllPagesAfterGlobalChangeInEnvironment();
133}
134
135RenderThemeGtk::RenderThemeGtk()
136{
137 static bool themeMonitorInitialized = false;
138 if (!themeMonitorInitialized) {
139 GtkSettings* settings = gtk_settings_get_default();
140 g_signal_connect(settings, "notify::gtk-theme-name", G_CALLBACK(themeChangedCallback), nullptr);
141 g_signal_connect(settings, "notify::gtk-color-scheme", G_CALLBACK(themeChangedCallback), nullptr);
142 themeMonitorInitialized = true;
143 }
144}
145
146enum RenderThemePart {
147 Entry,
148 EntrySelection,
149 EntryIconLeft,
150 EntryIconRight,
151 Button,
152 CheckButton,
153 RadioButton,
154 ComboBox,
155 ComboBoxButton,
156 ComboBoxArrow,
157 Scale,
158 ScaleTrough,
159 ScaleSlider,
160 ProgressBar,
161 ProgressBarTrough,
162 ProgressBarProgress,
163 ListBox,
164 SpinButton,
165 SpinButtonUpButton,
166 SpinButtonDownButton,
167#if ENABLE(VIDEO)
168 MediaButton,
169#endif
170#if GTK_CHECK_VERSION(3, 20, 0)
171 Window,
172#endif
173};
174
175#if !GTK_CHECK_VERSION(3, 20, 0)
176// This is the default value defined by GTK+, where it was defined as MIN_ARROW_SIZE in gtkarrow.c.
177static const int minArrowSize = 15;
178// This is the default value defined by GTK+, where it was defined as MIN_ARROW_WIDTH in gtkspinbutton.c.
179static const int minSpinButtonArrowSize = 6;
180
181static GRefPtr<GtkStyleContext> createStyleContext(RenderThemePart themePart, GtkStyleContext* parent = nullptr)
182{
183 GRefPtr<GtkWidgetPath> path = adoptGRef(parent ? gtk_widget_path_copy(gtk_style_context_get_path(parent)) : gtk_widget_path_new());
184
185 switch (themePart) {
186 case Entry:
187 case EntrySelection:
188 gtk_widget_path_append_type(path.get(), GTK_TYPE_ENTRY);
189 gtk_widget_path_iter_add_class(path.get(), -1, GTK_STYLE_CLASS_ENTRY);
190 break;
191 case EntryIconLeft:
192 case EntryIconRight:
193 gtk_widget_path_append_type(path.get(), GTK_TYPE_ENTRY);
194 break;
195 case Button:
196 gtk_widget_path_append_type(path.get(), GTK_TYPE_BUTTON);
197 gtk_widget_path_iter_add_class(path.get(), -1, GTK_STYLE_CLASS_BUTTON);
198 gtk_widget_path_iter_add_class(path.get(), -1, "text-button");
199 break;
200 case CheckButton:
201 gtk_widget_path_append_type(path.get(), GTK_TYPE_CHECK_BUTTON);
202 gtk_widget_path_iter_add_class(path.get(), -1, GTK_STYLE_CLASS_CHECK);
203 break;
204 case RadioButton:
205 gtk_widget_path_append_type(path.get(), GTK_TYPE_RADIO_BUTTON);
206 gtk_widget_path_iter_add_class(path.get(), -1, GTK_STYLE_CLASS_RADIO);
207 break;
208 case ComboBox:
209 gtk_widget_path_append_type(path.get(), GTK_TYPE_COMBO_BOX);
210 break;
211 case ComboBoxButton:
212 gtk_widget_path_append_type(path.get(), GTK_TYPE_BUTTON);
213 gtk_widget_path_iter_add_class(path.get(), -1, GTK_STYLE_CLASS_BUTTON);
214 gtk_widget_path_iter_add_class(path.get(), -1, "text-button");
215 gtk_widget_path_iter_add_class(path.get(), -1, "combo");
216 break;
217 case ComboBoxArrow:
218 gtk_widget_path_append_type(path.get(), GTK_TYPE_ARROW);
219 gtk_widget_path_iter_add_class(path.get(), -1, "arrow");
220 break;
221 case Scale:
222 gtk_widget_path_append_type(path.get(), GTK_TYPE_SCALE);
223 gtk_widget_path_iter_add_class(path.get(), -1, GTK_STYLE_CLASS_SCALE);
224 break;
225 case ScaleTrough:
226 gtk_widget_path_append_type(path.get(), GTK_TYPE_SCALE);
227 gtk_widget_path_iter_add_class(path.get(), -1, GTK_STYLE_CLASS_SCALE);
228 gtk_widget_path_iter_add_class(path.get(), -1, GTK_STYLE_CLASS_TROUGH);
229 break;
230 case ScaleSlider:
231 gtk_widget_path_append_type(path.get(), GTK_TYPE_SCALE);
232 gtk_widget_path_iter_add_class(path.get(), -1, GTK_STYLE_CLASS_SCALE);
233 gtk_widget_path_iter_add_class(path.get(), -1, GTK_STYLE_CLASS_SLIDER);
234 break;
235 case ProgressBar:
236 gtk_widget_path_append_type(path.get(), GTK_TYPE_PROGRESS_BAR);
237 gtk_widget_path_iter_add_class(path.get(), -1, GTK_STYLE_CLASS_PROGRESSBAR);
238 gtk_widget_path_iter_add_class(path.get(), -1, GTK_STYLE_CLASS_HORIZONTAL);
239 break;
240 case ProgressBarTrough:
241 gtk_widget_path_append_type(path.get(), GTK_TYPE_PROGRESS_BAR);
242 gtk_widget_path_iter_add_class(path.get(), -1, GTK_STYLE_CLASS_TROUGH);
243 break;
244 case ProgressBarProgress:
245 gtk_widget_path_append_type(path.get(), GTK_TYPE_PROGRESS_BAR);
246 gtk_widget_path_iter_add_class(path.get(), -1, GTK_STYLE_CLASS_PROGRESSBAR);
247 break;
248 case ListBox:
249 gtk_widget_path_append_type(path.get(), GTK_TYPE_TREE_VIEW);
250 gtk_widget_path_iter_add_class(path.get(), -1, GTK_STYLE_CLASS_VIEW);
251 break;
252 case SpinButton:
253 gtk_widget_path_append_type(path.get(), GTK_TYPE_SPIN_BUTTON);
254 gtk_widget_path_iter_add_class(path.get(), -1, GTK_STYLE_CLASS_SPINBUTTON);
255 gtk_widget_path_iter_add_class(path.get(), -1, GTK_STYLE_CLASS_HORIZONTAL);
256 break;
257 case SpinButtonUpButton:
258 case SpinButtonDownButton:
259 gtk_widget_path_append_type(path.get(), GTK_TYPE_SPIN_BUTTON);
260 gtk_widget_path_iter_add_class(path.get(), -1, GTK_STYLE_CLASS_SPINBUTTON);
261 gtk_widget_path_iter_add_class(path.get(), -1, GTK_STYLE_CLASS_BUTTON);
262 break;
263#if ENABLE(VIDEO)
264 case MediaButton:
265 gtk_widget_path_append_type(path.get(), GTK_TYPE_IMAGE);
266 gtk_widget_path_iter_add_class(path.get(), -1, GTK_STYLE_CLASS_IMAGE);
267 break;
268#endif // ENABLE(VIDEO)
269 default:
270 ASSERT_NOT_REACHED();
271 break;
272 }
273
274 GRefPtr<GtkStyleContext> context = adoptGRef(gtk_style_context_new());
275 gtk_style_context_set_path(context.get(), path.get());
276 gtk_style_context_set_parent(context.get(), parent);
277 return context;
278}
279
280static GRefPtr<GdkPixbuf> loadThemedIcon(GtkStyleContext* context, const char* iconName, GtkIconSize iconSize)
281{
282 GRefPtr<GIcon> icon = adoptGRef(g_themed_icon_new(iconName));
283 unsigned lookupFlags = GTK_ICON_LOOKUP_USE_BUILTIN | GTK_ICON_LOOKUP_FORCE_SIZE | GTK_ICON_LOOKUP_FORCE_SVG;
284#if GTK_CHECK_VERSION(3, 14, 0)
285 GtkTextDirection direction = gtk_style_context_get_direction(context);
286 if (direction & GTK_TEXT_DIR_LTR)
287 lookupFlags |= GTK_ICON_LOOKUP_DIR_LTR;
288 else if (direction & GTK_TEXT_DIR_RTL)
289 lookupFlags |= GTK_ICON_LOOKUP_DIR_RTL;
290#endif
291 int width, height;
292 gtk_icon_size_lookup(iconSize, &width, &height);
293 GRefPtr<GtkIconInfo> iconInfo = adoptGRef(gtk_icon_theme_lookup_by_gicon(gtk_icon_theme_get_default(), icon.get(), std::min(width, height), static_cast<GtkIconLookupFlags>(lookupFlags)));
294 if (!iconInfo)
295 return nullptr;
296
297 return adoptGRef(gtk_icon_info_load_symbolic_for_context(iconInfo.get(), context, nullptr, nullptr));
298}
299#endif // !GTK_CHECK_VERSION(3, 20, 0)
300
301#if ENABLE(VIDEO)
302static bool nodeHasPseudo(Node& node, const char* pseudo)
303{
304 return is<Element>(node) && downcast<Element>(node).pseudo() == pseudo;
305}
306
307static bool nodeHasClass(Node* node, const char* className)
308{
309 if (!is<Element>(*node))
310 return false;
311
312 Element& element = downcast<Element>(*node);
313
314 if (!element.hasClass())
315 return false;
316
317 return element.classNames().contains(className);
318}
319#endif // ENABLE(VIDEO)
320
321RenderThemeGtk::~RenderThemeGtk() = default;
322
323static bool supportsFocus(ControlPart appearance)
324{
325 switch (appearance) {
326 case PushButtonPart:
327 case ButtonPart:
328 case TextFieldPart:
329 case TextAreaPart:
330 case SearchFieldPart:
331 case MenulistPart:
332 case RadioPart:
333 case CheckboxPart:
334 case SliderHorizontalPart:
335 case SliderVerticalPart:
336 return true;
337 default:
338 return false;
339 }
340}
341
342bool RenderThemeGtk::supportsFocusRing(const RenderStyle& style) const
343{
344 return supportsFocus(style.appearance());
345}
346
347bool RenderThemeGtk::controlSupportsTints(const RenderObject& o) const
348{
349 return isEnabled(o);
350}
351
352int RenderThemeGtk::baselinePosition(const RenderBox& box) const
353{
354 // FIXME: This strategy is possibly incorrect for the GTK+ port.
355 if (box.style().appearance() == CheckboxPart || box.style().appearance() == RadioPart)
356 return box.marginTop() + box.height() - 2;
357 return RenderTheme::baselinePosition(box);
358}
359
360#if GTK_CHECK_VERSION(3, 20, 0)
361void RenderThemeGtk::adjustRepaintRect(const RenderObject&, FloatRect&)
362{
363}
364static GtkStateFlags themePartStateFlags(const RenderThemeGtk& theme, RenderThemePart themePart, const RenderObject& renderObject)
365{
366 unsigned stateFlags = 0;
367 switch (renderObject.style().direction()) {
368 case TextDirection::RTL:
369 stateFlags |= GTK_STATE_FLAG_DIR_RTL;
370 break;
371 case TextDirection::LTR:
372 stateFlags |= GTK_STATE_FLAG_DIR_LTR;
373 break;
374 }
375
376 if (!theme.isEnabled(renderObject) || (themePart == Entry && theme.isReadOnlyControl(renderObject)))
377 stateFlags |= GTK_STATE_FLAG_INSENSITIVE;
378 else {
379 if (theme.isHovered(renderObject))
380 stateFlags |= GTK_STATE_FLAG_PRELIGHT;
381 if (theme.isFocused(renderObject))
382 stateFlags |= GTK_STATE_FLAG_FOCUSED;
383 }
384
385 switch (themePart) {
386 case CheckButton:
387 case RadioButton:
388 if (theme.isChecked(renderObject))
389 stateFlags |= GTK_STATE_FLAG_CHECKED;
390 if (theme.isIndeterminate(renderObject))
391 stateFlags |= GTK_STATE_FLAG_INCONSISTENT;
392 if (theme.isPressed(renderObject))
393 stateFlags |= GTK_STATE_FLAG_SELECTED;
394 break;
395 case Button:
396 case ComboBoxButton:
397 case ScaleSlider:
398 case EntryIconLeft:
399 case EntryIconRight:
400#if ENABLE(VIDEO)
401 case MediaButton:
402#endif
403 if (theme.isPressed(renderObject))
404 stateFlags |= GTK_STATE_FLAG_ACTIVE;
405 break;
406 case SpinButtonUpButton:
407 if (theme.isPressed(renderObject) && theme.isSpinUpButtonPartPressed(renderObject))
408 stateFlags |= GTK_STATE_FLAG_ACTIVE;
409 if (theme.isHovered(renderObject) && !theme.isSpinUpButtonPartHovered(renderObject))
410 stateFlags &= ~GTK_STATE_FLAG_PRELIGHT;
411 break;
412 case SpinButtonDownButton:
413 if (theme.isPressed(renderObject) && !theme.isSpinUpButtonPartPressed(renderObject))
414 stateFlags |= GTK_STATE_FLAG_ACTIVE;
415 if (theme.isHovered(renderObject) && theme.isSpinUpButtonPartHovered(renderObject))
416 stateFlags &= ~GTK_STATE_FLAG_PRELIGHT;
417 break;
418 default:
419 break;
420 }
421
422 return static_cast<GtkStateFlags>(stateFlags);
423}
424#else
425static GtkTextDirection gtkTextDirection(TextDirection direction)
426{
427 switch (direction) {
428 case TextDirection::RTL:
429 return GTK_TEXT_DIR_RTL;
430 case TextDirection::LTR:
431 return GTK_TEXT_DIR_LTR;
432 default:
433 return GTK_TEXT_DIR_NONE;
434 }
435}
436
437static GtkStateFlags gtkIconStateFlags(RenderTheme* theme, const RenderObject& renderObject)
438{
439 if (!theme->isEnabled(renderObject))
440 return GTK_STATE_FLAG_INSENSITIVE;
441 if (theme->isPressed(renderObject))
442 return GTK_STATE_FLAG_ACTIVE;
443 if (theme->isHovered(renderObject))
444 return GTK_STATE_FLAG_PRELIGHT;
445
446 return GTK_STATE_FLAG_NORMAL;
447}
448
449static void adjustRectForFocus(GtkStyleContext* context, FloatRect& rect)
450{
451 gint focusWidth, focusPad;
452 gtk_style_context_get_style(context, "focus-line-width", &focusWidth, "focus-padding", &focusPad, nullptr);
453 rect.inflate(focusWidth + focusPad);
454}
455
456void RenderThemeGtk::adjustRepaintRect(const RenderObject& renderObject, FloatRect& rect)
457{
458 GRefPtr<GtkStyleContext> context;
459 bool checkInteriorFocus = false;
460 ControlPart part = renderObject.style().appearance();
461 switch (part) {
462 case CheckboxPart:
463 case RadioPart:
464 context = createStyleContext(part == CheckboxPart ? CheckButton : RadioButton);
465
466 gint indicatorSpacing;
467 gtk_style_context_get_style(context.get(), "indicator-spacing", &indicatorSpacing, nullptr);
468 rect.inflate(indicatorSpacing);
469
470 return;
471 case SliderVerticalPart:
472 case SliderHorizontalPart:
473 context = createStyleContext(ScaleSlider);
474 break;
475 case ButtonPart:
476 case MenulistButtonPart:
477 case MenulistPart:
478 context = createStyleContext(Button);
479 checkInteriorFocus = true;
480 break;
481 case TextFieldPart:
482 case TextAreaPart:
483 context = createStyleContext(Entry);
484 checkInteriorFocus = true;
485 break;
486 default:
487 return;
488 }
489
490 ASSERT(context);
491 if (checkInteriorFocus) {
492 gboolean interiorFocus;
493 gtk_style_context_get_style(context.get(), "interior-focus", &interiorFocus, nullptr);
494 if (interiorFocus)
495 return;
496 }
497 adjustRectForFocus(context.get(), rect);
498}
499#endif // GTK_CHECK_VERSION(3, 20, 0)
500
501void RenderThemeGtk::adjustButtonStyle(StyleResolver&, RenderStyle& style, const Element*) const
502{
503 // Some layout tests check explicitly that buttons ignore line-height.
504 if (style.appearance() == PushButtonPart)
505 style.setLineHeight(RenderStyle::initialLineHeight());
506}
507
508static void shrinkToMinimumSizeAndCenterRectangle(FloatRect& rect, const IntSize& minSize)
509{
510 if (rect.width() > minSize.width()) {
511 rect.inflateX(-(rect.width() - minSize.width()) / 2);
512 rect.setWidth(minSize.width()); // In case rect.width() was equal to minSize.width() + 1.
513 }
514
515 if (rect.height() > minSize.height()) {
516 rect.inflateY(-(rect.height() - minSize.height()) / 2);
517 rect.setHeight(minSize.height()); // In case rect.height() was equal to minSize.height() + 1.
518 }
519}
520
521#if GTK_CHECK_VERSION(3, 20, 0)
522static void setToggleSize(RenderThemePart themePart, RenderStyle& style)
523{
524 ASSERT(themePart == CheckButton || themePart == RadioButton);
525
526 // The width and height are both specified, so we shouldn't change them.
527 if (!style.width().isIntrinsicOrAuto() && !style.height().isAuto())
528 return;
529
530 auto& toggleWidget = static_cast<RenderThemeToggleButton&>(RenderThemeWidget::getOrCreate(themePart == CheckButton ? RenderThemeWidget::Type::CheckButton : RenderThemeWidget::Type::RadioButton));
531 toggleWidget.button().setState(GTK_STATE_FLAG_NORMAL);
532 toggleWidget.toggle().setState(GTK_STATE_FLAG_NORMAL);
533 IntSize preferredSize = toggleWidget.button().preferredSize();
534 preferredSize = preferredSize.expandedTo(toggleWidget.toggle().preferredSize());
535
536 if (style.width().isIntrinsicOrAuto())
537 style.setWidth(Length(preferredSize.width(), Fixed));
538
539 if (style.height().isAuto())
540 style.setHeight(Length(preferredSize.height(), Fixed));
541}
542
543static void paintToggle(const RenderThemeGtk* theme, RenderThemePart themePart, const RenderObject& renderObject, const PaintInfo& paintInfo, const IntRect& fullRect)
544{
545 ASSERT(themePart == CheckButton || themePart == RadioButton);
546
547 auto& toggleWidget = static_cast<RenderThemeToggleButton&>(RenderThemeWidget::getOrCreate(themePart == CheckButton ? RenderThemeWidget::Type::CheckButton : RenderThemeWidget::Type::RadioButton));
548 auto toggleState = themePartStateFlags(*theme, themePart, renderObject);
549 toggleWidget.button().setState(toggleState);
550 toggleWidget.toggle().setState(toggleState);
551
552 FloatRect rect = fullRect;
553 // Some themes do not render large toggle buttons properly, so we simply
554 // shrink the rectangle back down to the default size and then center it
555 // in the full toggle button region. The reason for not simply forcing toggle
556 // buttons to be a smaller size is that we don't want to break site layouts.
557 IntSize preferredSize = toggleWidget.button().preferredSize();
558 preferredSize = preferredSize.expandedTo(toggleWidget.toggle().preferredSize());
559 shrinkToMinimumSizeAndCenterRectangle(rect, preferredSize);
560 toggleWidget.button().render(paintInfo.context().platformContext()->cr(), rect);
561 toggleWidget.toggle().render(paintInfo.context().platformContext()->cr(), rect);
562
563 if (theme->isFocused(renderObject))
564 toggleWidget.button().renderFocus(paintInfo.context().platformContext()->cr(), rect);
565}
566#else
567static void setToggleSize(RenderThemePart themePart, RenderStyle& style)
568{
569 // The width and height are both specified, so we shouldn't change them.
570 if (!style.width().isIntrinsicOrAuto() && !style.height().isAuto())
571 return;
572
573 GRefPtr<GtkStyleContext> context = createStyleContext(themePart);
574 // Other ports hard-code this to 13. GTK+ users tend to demand the native look.
575 gint indicatorSize;
576 gtk_style_context_get_style(context.get(), "indicator-size", &indicatorSize, nullptr);
577
578 if (style.width().isIntrinsicOrAuto())
579 style.setWidth(Length(indicatorSize, Fixed));
580
581 if (style.height().isAuto())
582 style.setHeight(Length(indicatorSize, Fixed));
583}
584
585static void paintToggle(const RenderThemeGtk* theme, RenderThemePart themePart, const RenderObject& renderObject, const PaintInfo& paintInfo, const IntRect& fullRect)
586{
587 GRefPtr<GtkStyleContext> context = createStyleContext(themePart);
588 gtk_style_context_set_direction(context.get(), static_cast<GtkTextDirection>(gtkTextDirection(renderObject.style().direction())));
589
590 unsigned flags = 0;
591 if (!theme->isEnabled(renderObject))
592 flags |= GTK_STATE_FLAG_INSENSITIVE;
593 else if (theme->isHovered(renderObject))
594 flags |= GTK_STATE_FLAG_PRELIGHT;
595 if (theme->isIndeterminate(renderObject))
596 flags |= GTK_STATE_FLAG_INCONSISTENT;
597 else if (theme->isChecked(renderObject))
598#if GTK_CHECK_VERSION(3, 13, 7)
599 flags |= GTK_STATE_FLAG_CHECKED;
600#else
601 flags |= GTK_STATE_FLAG_ACTIVE;
602#endif
603 if (theme->isPressed(renderObject))
604 flags |= GTK_STATE_FLAG_SELECTED;
605 gtk_style_context_set_state(context.get(), static_cast<GtkStateFlags>(flags));
606
607 // Some themes do not render large toggle buttons properly, so we simply
608 // shrink the rectangle back down to the default size and then center it
609 // in the full toggle button region. The reason for not simply forcing toggle
610 // buttons to be a smaller size is that we don't want to break site layouts.
611 FloatRect rect(fullRect);
612 gint indicatorSize;
613 gtk_style_context_get_style(context.get(), "indicator-size", &indicatorSize, nullptr);
614 IntSize minSize(indicatorSize, indicatorSize);
615 shrinkToMinimumSizeAndCenterRectangle(rect, minSize);
616
617 gtk_render_background(context.get(), paintInfo.context().platformContext()->cr(), rect.x(), rect.y(), rect.width(), rect.height());
618 gtk_render_frame(context.get(), paintInfo.context().platformContext()->cr(), rect.x(), rect.y(), rect.width(), rect.height());
619
620 if (themePart == CheckButton)
621 gtk_render_check(context.get(), paintInfo.context().platformContext()->cr(), rect.x(), rect.y(), rect.width(), rect.height());
622 else
623 gtk_render_option(context.get(), paintInfo.context().platformContext()->cr(), rect.x(), rect.y(), rect.width(), rect.height());
624
625 if (theme->isFocused(renderObject)) {
626 IntRect indicatorRect(rect);
627 gint indicatorSpacing;
628 gtk_style_context_get_style(context.get(), "indicator-spacing", &indicatorSpacing, nullptr);
629 indicatorRect.inflate(indicatorSpacing);
630 gtk_render_focus(context.get(), paintInfo.context().platformContext()->cr(), indicatorRect.x(), indicatorRect.y(),
631 indicatorRect.width(), indicatorRect.height());
632 }
633}
634#endif // GTK_CHECK_VERSION(3, 20, 0)
635
636void RenderThemeGtk::setCheckboxSize(RenderStyle& style) const
637{
638 setToggleSize(CheckButton, style);
639}
640
641bool RenderThemeGtk::paintCheckbox(const RenderObject& renderObject, const PaintInfo& paintInfo, const IntRect& rect)
642{
643 paintToggle(this, CheckButton, renderObject, paintInfo, rect);
644 return false;
645}
646
647void RenderThemeGtk::setRadioSize(RenderStyle& style) const
648{
649 setToggleSize(RadioButton, style);
650}
651
652bool RenderThemeGtk::paintRadio(const RenderObject& renderObject, const PaintInfo& paintInfo, const IntRect& rect)
653{
654 paintToggle(this, RadioButton, renderObject, paintInfo, rect);
655 return false;
656}
657
658#if GTK_CHECK_VERSION(3, 20, 0)
659bool RenderThemeGtk::paintButton(const RenderObject& renderObject, const PaintInfo& paintInfo, const IntRect& rect)
660{
661 auto& buttonWidget = static_cast<RenderThemeButton&>(RenderThemeWidget::getOrCreate(isDefault(renderObject) ? RenderThemeWidget::Type::ButtonDefault : RenderThemeWidget::Type::Button));
662 buttonWidget.button().setState(themePartStateFlags(*this, Button, renderObject));
663 buttonWidget.button().render(paintInfo.context().platformContext()->cr(), rect);
664 if (isFocused(renderObject))
665 buttonWidget.button().renderFocus(paintInfo.context().platformContext()->cr(), rect);
666 return false;
667}
668#else
669static void renderButton(RenderTheme* theme, GtkStyleContext* context, const RenderObject& renderObject, const PaintInfo& paintInfo, const IntRect& rect)
670{
671 IntRect buttonRect(rect);
672
673 guint flags = 0;
674 if (!theme->isEnabled(renderObject))
675 flags |= GTK_STATE_FLAG_INSENSITIVE;
676 else if (theme->isHovered(renderObject))
677 flags |= GTK_STATE_FLAG_PRELIGHT;
678 if (theme->isPressed(renderObject))
679 flags |= GTK_STATE_FLAG_ACTIVE;
680 gtk_style_context_set_state(context, static_cast<GtkStateFlags>(flags));
681
682 if (theme->isDefault(renderObject)) {
683 GtkBorder* borderPtr = 0;
684 GtkBorder border = { 1, 1, 1, 1 };
685
686 gtk_style_context_get_style(context, "default-border", &borderPtr, nullptr);
687 if (borderPtr) {
688 border = *borderPtr;
689 gtk_border_free(borderPtr);
690 }
691
692 buttonRect.move(border.left, border.top);
693 buttonRect.setWidth(buttonRect.width() - (border.left + border.right));
694 buttonRect.setHeight(buttonRect.height() - (border.top + border.bottom));
695
696 gtk_style_context_add_class(context, GTK_STYLE_CLASS_DEFAULT);
697 }
698
699 gtk_render_background(context, paintInfo.context().platformContext()->cr(), buttonRect.x(), buttonRect.y(), buttonRect.width(), buttonRect.height());
700 gtk_render_frame(context, paintInfo.context().platformContext()->cr(), buttonRect.x(), buttonRect.y(), buttonRect.width(), buttonRect.height());
701
702 if (theme->isFocused(renderObject)) {
703 gint focusWidth, focusPad;
704 gboolean displaceFocus, interiorFocus;
705 gtk_style_context_get_style(
706 context,
707 "focus-line-width", &focusWidth,
708 "focus-padding", &focusPad,
709 "interior-focus", &interiorFocus,
710 "displace-focus", &displaceFocus,
711 nullptr);
712
713 if (interiorFocus) {
714 GtkBorder borderWidth;
715 gtk_style_context_get_border(context, gtk_style_context_get_state(context), &borderWidth);
716
717 buttonRect = IntRect(
718 buttonRect.x() + borderWidth.left + focusPad,
719 buttonRect.y() + borderWidth.top + focusPad,
720 buttonRect.width() - (2 * focusPad + borderWidth.left + borderWidth.right),
721 buttonRect.height() - (2 * focusPad + borderWidth.top + borderWidth.bottom));
722 } else
723 buttonRect.inflate(focusWidth + focusPad);
724
725 if (displaceFocus && theme->isPressed(renderObject)) {
726 gint childDisplacementX;
727 gint childDisplacementY;
728 gtk_style_context_get_style(context, "child-displacement-x", &childDisplacementX, "child-displacement-y", &childDisplacementY, nullptr);
729 buttonRect.move(childDisplacementX, childDisplacementY);
730 }
731
732 gtk_render_focus(context, paintInfo.context().platformContext()->cr(), buttonRect.x(), buttonRect.y(), buttonRect.width(), buttonRect.height());
733 }
734}
735bool RenderThemeGtk::paintButton(const RenderObject& renderObject, const PaintInfo& paintInfo, const IntRect& rect)
736{
737 GRefPtr<GtkStyleContext> context = createStyleContext(Button);
738 gtk_style_context_set_direction(context.get(), static_cast<GtkTextDirection>(gtkTextDirection(renderObject.style().direction())));
739 renderButton(this, context.get(), renderObject, paintInfo, rect);
740 return false;
741}
742#endif // GTK_CHECK_VERSION(3, 20, 0)
743
744static Color menuListColor(const Element* element)
745{
746#if GTK_CHECK_VERSION(3, 20, 0)
747 auto& comboWidget = static_cast<RenderThemeComboBox&>(RenderThemeWidget::getOrCreate(RenderThemeWidget::Type::ComboBox));
748 GtkStateFlags state = element->isDisabledFormControl() ? GTK_STATE_FLAG_INSENSITIVE : GTK_STATE_FLAG_NORMAL;
749 comboWidget.comboBox().setState(state);
750 comboWidget.button().setState(state);
751 return comboWidget.button().color();
752#else
753 GRefPtr<GtkStyleContext> parentStyleContext = createStyleContext(ComboBox);
754 GRefPtr<GtkStyleContext> buttonStyleContext = createStyleContext(ComboBoxButton, parentStyleContext.get());
755 gtk_style_context_set_state(buttonStyleContext.get(), element->isDisabledFormControl() ? GTK_STATE_FLAG_INSENSITIVE : GTK_STATE_FLAG_NORMAL);
756
757 GdkRGBA gdkRGBAColor;
758 gtk_style_context_get_color(buttonStyleContext.get(), gtk_style_context_get_state(buttonStyleContext.get()), &gdkRGBAColor);
759 return gdkRGBAColor;
760#endif // GTK_CHECK_VERSION(3, 20, 0)
761}
762
763void RenderThemeGtk::adjustMenuListStyle(StyleResolver&, RenderStyle& style, const Element* element) const
764{
765 // The tests check explicitly that select menu buttons ignore line height.
766 style.setLineHeight(RenderStyle::initialLineHeight());
767
768 // We cannot give a proper rendering when border radius is active, unfortunately.
769 style.resetBorderRadius();
770
771 if (element)
772 style.setColor(menuListColor(element));
773}
774
775void RenderThemeGtk::adjustMenuListButtonStyle(StyleResolver& styleResolver, RenderStyle& style, const Element* e) const
776{
777 adjustMenuListStyle(styleResolver, style, e);
778}
779
780#if GTK_CHECK_VERSION(3, 20, 0)
781/*
782 * GtkComboBox gadgets tree
783 *
784 * combobox
785 * ├── box.linked
786 * │ ╰── button.combo
787 * │ ╰── box
788 * │ ├── cellview
789 * │ ╰── arrow
790 * ╰── window.popup
791 */
792LengthBox RenderThemeGtk::popupInternalPaddingBox(const RenderStyle& style) const
793{
794 if (style.appearance() == NoControlPart)
795 return LengthBox(0);
796
797 auto& comboWidget = static_cast<RenderThemeComboBox&>(RenderThemeWidget::getOrCreate(RenderThemeWidget::Type::ComboBox));
798 comboWidget.comboBox().setState(GTK_STATE_FLAG_NORMAL);
799 comboWidget.button().setState(GTK_STATE_FLAG_NORMAL);
800 comboWidget.arrow().setState(GTK_STATE_FLAG_NORMAL);
801 GtkBorder comboContentsBox = comboWidget.comboBox().contentsBox();
802 GtkBorder boxContentsBox = comboWidget.box().contentsBox();
803 GtkBorder buttonContentsBox = comboWidget.button().contentsBox();
804 GtkBorder buttonBoxContentsBox = comboWidget.buttonBox().contentsBox();
805 GtkBorder padding;
806 padding.left = comboContentsBox.left + boxContentsBox.left + buttonContentsBox.left + buttonBoxContentsBox.left;
807 padding.right = comboContentsBox.right + boxContentsBox.right + buttonContentsBox.right + buttonBoxContentsBox.right;
808 padding.top = comboContentsBox.top + boxContentsBox.top + buttonContentsBox.top + buttonBoxContentsBox.top;
809 padding.bottom = comboContentsBox.bottom + boxContentsBox.bottom + buttonContentsBox.bottom + buttonBoxContentsBox.bottom;
810
811 auto arrowSize = comboWidget.arrow().preferredSize();
812 return LengthBox(padding.top, padding.right + (style.direction() == TextDirection::LTR ? arrowSize.width() : 0),
813 padding.bottom, padding.left + (style.direction() == TextDirection::RTL ? arrowSize.width() : 0));
814}
815
816bool RenderThemeGtk::paintMenuList(const RenderObject& renderObject, const PaintInfo& paintInfo, const FloatRect& rect)
817{
818 auto& comboWidget = static_cast<RenderThemeComboBox&>(RenderThemeWidget::getOrCreate(RenderThemeWidget::Type::ComboBox));
819 auto comboState = themePartStateFlags(*this, ComboBoxButton, renderObject);
820 comboWidget.comboBox().setState(comboState);
821 comboWidget.button().setState(comboState);
822 comboWidget.arrow().setState(comboState);
823
824 cairo_t* cr = paintInfo.context().platformContext()->cr();
825 comboWidget.comboBox().render(cr, rect);
826 comboWidget.box().render(cr, rect);
827 FloatRect contentsRect;
828 comboWidget.button().render(cr, rect, &contentsRect);
829 comboWidget.buttonBox().render(cr, contentsRect);
830 comboWidget.arrow().render(cr, contentsRect);
831 if (isFocused(renderObject))
832 comboWidget.button().renderFocus(cr, rect);
833
834 return false;
835}
836#else
837LengthBox RenderThemeGtk::popupInternalPaddingBox(const RenderStyle& style) const
838{
839 if (style.appearance() == NoControlPart)
840 return { 0, 0, 0, 0 };
841
842 GRefPtr<GtkStyleContext> parentContext = createStyleContext(ComboBox);
843 GRefPtr<GtkStyleContext> context = createStyleContext(ComboBoxButton, parentContext.get());
844 gtk_style_context_set_direction(context.get(), static_cast<GtkTextDirection>(gtkTextDirection(style.direction())));
845 gtk_style_context_set_state(context.get(), static_cast<GtkStateFlags>(0));
846 GtkBorder borderWidth = { 0, 0, 0, 0 };
847 gtk_style_context_get_border(context.get(), gtk_style_context_get_state(context.get()), &borderWidth);
848
849 gboolean interiorFocus;
850 gint focusWidth, focusPad;
851 gtk_style_context_get_style(context.get(), "interior-focus", &interiorFocus, "focus-line-width", &focusWidth, "focus-padding", &focusPad, nullptr);
852 focusWidth = interiorFocus ? focusWidth + focusPad : 0;
853
854 return { borderWidth.top + focusWidth, borderWidth.right + focusWidth + (style.direction() == TextDirection::LTR ? minArrowSize : 0),
855 borderWidth.bottom + focusWidth, borderWidth.left + focusWidth + (style.direction() == TextDirection::RTL ? minArrowSize : 0) };
856}
857
858bool RenderThemeGtk::paintMenuList(const RenderObject& renderObject, const PaintInfo& paintInfo, const FloatRect& r)
859{
860 // FIXME: adopt subpixel themes.
861 IntRect rect = IntRect(r);
862
863 cairo_t* cairoContext = paintInfo.context().platformContext()->cr();
864 GtkTextDirection direction = static_cast<GtkTextDirection>(gtkTextDirection(renderObject.style().direction()));
865
866 GRefPtr<GtkStyleContext> parentStyleContext = createStyleContext(ComboBox);
867
868 // Paint the button.
869 GRefPtr<GtkStyleContext> buttonStyleContext = createStyleContext(ComboBoxButton, parentStyleContext.get());
870 gtk_style_context_set_direction(buttonStyleContext.get(), direction);
871 renderButton(this, buttonStyleContext.get(), renderObject, paintInfo, rect);
872
873 // Get the inner rectangle.
874 gint focusWidth, focusPad;
875 GtkBorder* innerBorderPtr = 0;
876 GtkBorder innerBorder = { 1, 1, 1, 1 };
877 gtk_style_context_get_style(buttonStyleContext.get(), "inner-border", &innerBorderPtr, "focus-line-width", &focusWidth, "focus-padding", &focusPad, nullptr);
878 if (innerBorderPtr) {
879 innerBorder = *innerBorderPtr;
880 gtk_border_free(innerBorderPtr);
881 }
882
883 GtkBorder borderWidth;
884 GtkStateFlags state = gtk_style_context_get_state(buttonStyleContext.get());
885 gtk_style_context_get_border(buttonStyleContext.get(), state, &borderWidth);
886
887 focusWidth += focusPad;
888 IntRect innerRect(
889 rect.x() + innerBorder.left + borderWidth.left + focusWidth,
890 rect.y() + innerBorder.top + borderWidth.top + focusWidth,
891 rect.width() - borderWidth.left - borderWidth.right - innerBorder.left - innerBorder.right - (2 * focusWidth),
892 rect.height() - borderWidth.top - borderWidth.bottom - innerBorder.top - innerBorder.bottom - (2 * focusWidth));
893
894 if (isPressed(renderObject)) {
895 gint childDisplacementX;
896 gint childDisplacementY;
897 gtk_style_context_get_style(buttonStyleContext.get(), "child-displacement-x", &childDisplacementX, "child-displacement-y", &childDisplacementY, nullptr);
898 innerRect.move(childDisplacementX, childDisplacementY);
899 }
900 innerRect.setWidth(std::max(1, innerRect.width()));
901 innerRect.setHeight(std::max(1, innerRect.height()));
902
903 // Paint the arrow.
904 GRefPtr<GtkStyleContext> arrowStyleContext = createStyleContext(ComboBoxArrow, buttonStyleContext.get());
905 gtk_style_context_set_direction(arrowStyleContext.get(), direction);
906
907 gfloat arrowScaling;
908 gtk_style_context_get_style(parentStyleContext.get(), "arrow-scaling", &arrowScaling, nullptr);
909
910 IntSize arrowSize(minArrowSize, innerRect.height());
911 FloatPoint arrowPosition(innerRect.location());
912 if (direction == GTK_TEXT_DIR_LTR)
913 arrowPosition.move(innerRect.width() - arrowSize.width(), 0);
914
915 // GTK+ actually fetches the xalign and valign values from the widget, but since we
916 // don't have a widget here, we are just using the default xalign and valign values of 0.5.
917 gint extent = std::min(arrowSize.width(), arrowSize.height()) * arrowScaling;
918 arrowPosition.move((arrowSize.width() - extent) / 2, (arrowSize.height() - extent) / 2);
919
920 gtk_style_context_set_state(arrowStyleContext.get(), state);
921 gtk_render_arrow(arrowStyleContext.get(), cairoContext, G_PI, arrowPosition.x(), arrowPosition.y(), extent);
922
923 return false;
924}
925#endif // GTK_CHECK_VERSION(3, 20, 0)
926
927bool RenderThemeGtk::paintMenuListButtonDecorations(const RenderBox& object, const PaintInfo& info, const FloatRect& rect)
928{
929 return paintMenuList(object, info, rect);
930}
931
932#if GTK_CHECK_VERSION(3, 20, 0)
933
934static IntSize spinButtonSize()
935{
936 auto& spinButtonWidget = static_cast<RenderThemeSpinButton&>(RenderThemeWidget::getOrCreate(RenderThemeWidget::Type::SpinButton));
937 spinButtonWidget.spinButton().setState(GTK_STATE_FLAG_NORMAL);
938 spinButtonWidget.entry().setState(GTK_STATE_FLAG_NORMAL);
939 spinButtonWidget.up().setState(GTK_STATE_FLAG_NORMAL);
940 spinButtonWidget.down().setState(GTK_STATE_FLAG_NORMAL);
941
942 IntSize preferredSize = spinButtonWidget.spinButton().preferredSize();
943 preferredSize = preferredSize.expandedTo(spinButtonWidget.entry().preferredSize());
944 IntSize upPreferredSize = preferredSize.expandedTo(spinButtonWidget.up().preferredSize());
945 IntSize downPreferredSize = preferredSize.expandedTo(spinButtonWidget.down().preferredSize());
946
947 return IntSize(upPreferredSize.width() + downPreferredSize.width(), std::max(upPreferredSize.height(), downPreferredSize.height()));
948}
949
950
951void RenderThemeGtk::adjustTextFieldStyle(StyleResolver&, RenderStyle& style, const Element* element) const
952{
953 if (!is<HTMLInputElement>(element) || !shouldHaveSpinButton(downcast<HTMLInputElement>(*element)))
954 return;
955
956 style.setMinHeight(Length(spinButtonSize().height(), Fixed));
957
958 // The default theme for the GTK+ port uses very wide spin buttons (66px) compared to what other
959 // browsers use (~13 px). And unfortunately, most of the web developers won't test how their site
960 // renders on WebKitGTK+. To ensure that spin buttons don't end up covering the values of the input
961 // field, we override the width of the input element and always increment it with the width needed
962 // for the spinbutton (when drawing the spinbutton).
963 int minimumWidth = style.width().intValue() + spinButtonSize().width();
964 style.setMinWidth(Length(minimumWidth, Fixed));
965}
966
967bool RenderThemeGtk::paintTextField(const RenderObject& renderObject, const PaintInfo& paintInfo, const FloatRect& rect)
968{
969 if (is<HTMLInputElement>(renderObject.node()) && shouldHaveSpinButton(downcast<HTMLInputElement>(*renderObject.node()))) {
970 auto& spinButtonWidget = static_cast<RenderThemeSpinButton&>(RenderThemeWidget::getOrCreate(RenderThemeWidget::Type::SpinButton));
971 auto spinButtonState = themePartStateFlags(*this, Entry, renderObject);
972 spinButtonWidget.spinButton().setState(spinButtonState);
973 spinButtonWidget.entry().setState(spinButtonState);
974 spinButtonWidget.spinButton().render(paintInfo.context().platformContext()->cr(), rect);
975 spinButtonWidget.entry().render(paintInfo.context().platformContext()->cr(), rect);
976 } else {
977 auto& entryWidget = static_cast<RenderThemeEntry&>(RenderThemeWidget::getOrCreate(RenderThemeWidget::Type::Entry));
978 entryWidget.entry().setState(themePartStateFlags(*this, Entry, renderObject));
979 entryWidget.entry().render(paintInfo.context().platformContext()->cr(), rect);
980 }
981 return false;
982}
983#else
984void RenderThemeGtk::adjustTextFieldStyle(StyleResolver&, RenderStyle&, const Element*) const
985{
986}
987
988bool RenderThemeGtk::paintTextField(const RenderObject& renderObject, const PaintInfo& paintInfo, const FloatRect& rect)
989{
990 GRefPtr<GtkStyleContext> context = createStyleContext(Entry);
991 gtk_style_context_set_direction(context.get(), static_cast<GtkTextDirection>(gtkTextDirection(renderObject.style().direction())));
992
993 guint flags = 0;
994 if (!isEnabled(renderObject) || isReadOnlyControl(renderObject))
995 flags |= GTK_STATE_FLAG_INSENSITIVE;
996 else if (isFocused(renderObject))
997 flags |= GTK_STATE_FLAG_FOCUSED;
998 gtk_style_context_set_state(context.get(), static_cast<GtkStateFlags>(flags));
999
1000 gtk_render_background(context.get(), paintInfo.context().platformContext()->cr(), rect.x(), rect.y(), rect.width(), rect.height());
1001 gtk_render_frame(context.get(), paintInfo.context().platformContext()->cr(), rect.x(), rect.y(), rect.width(), rect.height());
1002
1003 if (isFocused(renderObject) && isEnabled(renderObject)) {
1004 gboolean interiorFocus;
1005 gint focusWidth, focusPad;
1006 gtk_style_context_get_style(context.get(), "interior-focus", &interiorFocus, "focus-line-width", &focusWidth, "focus-padding", &focusPad, nullptr);
1007 if (!interiorFocus) {
1008 IntRect focusRect(rect);
1009 focusRect.inflate(focusWidth + focusPad);
1010 gtk_render_focus(context.get(), paintInfo.context().platformContext()->cr(), focusRect.x(), focusRect.y(), focusRect.width(), focusRect.height());
1011 }
1012 }
1013
1014 return false;
1015}
1016#endif
1017
1018#if GTK_CHECK_VERSION(3, 20, 0)
1019static void adjustSearchFieldIconStyle(RenderThemePart themePart, RenderStyle& style)
1020{
1021 ASSERT(themePart == EntryIconLeft || themePart == EntryIconRight);
1022 auto& searchEntryWidget = static_cast<RenderThemeSearchEntry&>(RenderThemeWidget::getOrCreate(RenderThemeWidget::Type::SearchEntry));
1023 searchEntryWidget.entry().setState(GTK_STATE_FLAG_NORMAL);
1024 searchEntryWidget.leftIcon().setState(GTK_STATE_FLAG_NORMAL);
1025 searchEntryWidget.rightIcon().setState(GTK_STATE_FLAG_NORMAL);
1026
1027 // Get the icon size based on the font size.
1028 auto& icon = static_cast<RenderThemeIconGadget&>(themePart == EntryIconLeft ? searchEntryWidget.leftIcon() : searchEntryWidget.rightIcon());
1029 icon.setIconSize(style.computedFontPixelSize());
1030 IntSize preferredSize = icon.preferredSize();
1031 GtkBorder contentsBox = searchEntryWidget.entry().contentsBox();
1032 if (themePart == EntryIconLeft)
1033 preferredSize.expand(contentsBox.left, contentsBox.top + contentsBox.bottom);
1034 else
1035 preferredSize.expand(contentsBox.right, contentsBox.top + contentsBox.bottom);
1036 style.setWidth(Length(preferredSize.width(), Fixed));
1037 style.setHeight(Length(preferredSize.height(), Fixed));
1038}
1039#else
1040// Defined in GTK+ (gtk/gtkiconfactory.c)
1041static const gint gtkIconSizeMenu = 16;
1042static const gint gtkIconSizeSmallToolbar = 18;
1043static const gint gtkIconSizeButton = 20;
1044static const gint gtkIconSizeLargeToolbar = 24;
1045static const gint gtkIconSizeDnd = 32;
1046static const gint gtkIconSizeDialog = 48;
1047
1048static GtkIconSize getIconSizeForPixelSize(gint pixelSize)
1049{
1050 if (pixelSize < gtkIconSizeSmallToolbar)
1051 return GTK_ICON_SIZE_MENU;
1052 if (pixelSize >= gtkIconSizeSmallToolbar && pixelSize < gtkIconSizeButton)
1053 return GTK_ICON_SIZE_SMALL_TOOLBAR;
1054 if (pixelSize >= gtkIconSizeButton && pixelSize < gtkIconSizeLargeToolbar)
1055 return GTK_ICON_SIZE_BUTTON;
1056 if (pixelSize >= gtkIconSizeLargeToolbar && pixelSize < gtkIconSizeDnd)
1057 return GTK_ICON_SIZE_LARGE_TOOLBAR;
1058 if (pixelSize >= gtkIconSizeDnd && pixelSize < gtkIconSizeDialog)
1059 return GTK_ICON_SIZE_DND;
1060
1061 return GTK_ICON_SIZE_DIALOG;
1062}
1063
1064static void adjustSearchFieldIconStyle(RenderThemePart themePart, RenderStyle& style)
1065{
1066 style.resetBorder();
1067 style.resetPadding();
1068
1069 GRefPtr<GtkStyleContext> parentContext = createStyleContext(Entry);
1070 GRefPtr<GtkStyleContext> context = createStyleContext(themePart, parentContext.get());
1071
1072 GtkBorder padding;
1073 gtk_style_context_get_padding(context.get(), gtk_style_context_get_state(context.get()), &padding);
1074
1075 // Get the icon size based on the font size.
1076 int fontSize = style.computedFontPixelSize();
1077 if (fontSize < gtkIconSizeMenu) {
1078 style.setWidth(Length(fontSize + (padding.left + padding.right), Fixed));
1079 style.setHeight(Length(fontSize + (padding.top + padding.bottom), Fixed));
1080 return;
1081 }
1082 gint width = 0, height = 0;
1083 gtk_icon_size_lookup(getIconSizeForPixelSize(fontSize), &width, &height);
1084 style.setWidth(Length(width + (padding.left + padding.right), Fixed));
1085 style.setHeight(Length(height + (padding.top + padding.bottom), Fixed));
1086}
1087#endif
1088
1089bool RenderThemeGtk::paintTextArea(const RenderObject& o, const PaintInfo& i, const FloatRect& r)
1090{
1091 return paintTextField(o, i, r);
1092}
1093
1094void RenderThemeGtk::adjustSearchFieldResultsButtonStyle(StyleResolver& styleResolver, RenderStyle& style, const Element* e) const
1095{
1096 adjustSearchFieldCancelButtonStyle(styleResolver, style, e);
1097}
1098
1099bool RenderThemeGtk::paintSearchFieldResultsButton(const RenderBox& o, const PaintInfo& i, const IntRect& rect)
1100{
1101 return paintSearchFieldResultsDecorationPart(o, i, rect);
1102}
1103
1104void RenderThemeGtk::adjustSearchFieldResultsDecorationPartStyle(StyleResolver&, RenderStyle& style, const Element*) const
1105{
1106 adjustSearchFieldIconStyle(EntryIconLeft, style);
1107}
1108
1109void RenderThemeGtk::adjustSearchFieldCancelButtonStyle(StyleResolver&, RenderStyle& style, const Element*) const
1110{
1111 adjustSearchFieldIconStyle(EntryIconRight, style);
1112}
1113
1114#if GTK_CHECK_VERSION(3, 20, 0)
1115static bool paintSearchFieldIcon(RenderThemeGtk* theme, RenderThemePart themePart, const RenderBox& renderObject, const PaintInfo& paintInfo, const IntRect& rect)
1116{
1117 ASSERT(themePart == EntryIconLeft || themePart == EntryIconRight);
1118 auto& searchEntryWidget = static_cast<RenderThemeSearchEntry&>(RenderThemeWidget::getOrCreate(RenderThemeWidget::Type::SearchEntry));
1119 searchEntryWidget.entry().setState(themePartStateFlags(*theme, Entry, renderObject));
1120 auto& icon = static_cast<RenderThemeIconGadget&>(themePart == EntryIconLeft ? searchEntryWidget.leftIcon() : searchEntryWidget.rightIcon());
1121 icon.setState(themePartStateFlags(*theme, themePart, renderObject));
1122 icon.setIconSize(renderObject.style().computedFontPixelSize());
1123 GtkBorder contentsBox = searchEntryWidget.entry().contentsBox();
1124 IntRect iconRect = rect;
1125 if (themePart == EntryIconLeft) {
1126 iconRect.move(contentsBox.left, contentsBox.top);
1127 iconRect.contract(contentsBox.left, contentsBox.top + contentsBox.bottom);
1128 } else
1129 iconRect.contract(contentsBox.right, contentsBox.top + contentsBox.bottom);
1130 return !icon.render(paintInfo.context().platformContext()->cr(), iconRect);
1131}
1132bool RenderThemeGtk::paintSearchFieldResultsDecorationPart(const RenderBox& renderObject, const PaintInfo& paintInfo, const IntRect& rect)
1133{
1134 return paintSearchFieldIcon(this, EntryIconLeft, renderObject, paintInfo, rect);
1135}
1136
1137bool RenderThemeGtk::paintSearchFieldCancelButton(const RenderBox& renderObject, const PaintInfo& paintInfo, const IntRect& rect)
1138{
1139 return paintSearchFieldIcon(this, EntryIconRight, renderObject, paintInfo, rect);
1140}
1141#else
1142static bool paintIcon(GtkStyleContext* context, GraphicsContext& graphicsContext, const IntRect& rect, const char* iconName)
1143{
1144 GRefPtr<GdkPixbuf> icon = loadThemedIcon(context, iconName, getIconSizeForPixelSize(rect.height()));
1145 if (!icon)
1146 return false;
1147
1148 if (gdk_pixbuf_get_width(icon.get()) > rect.width() || gdk_pixbuf_get_height(icon.get()) > rect.height())
1149 icon = adoptGRef(gdk_pixbuf_scale_simple(icon.get(), rect.width(), rect.height(), GDK_INTERP_BILINEAR));
1150
1151 gtk_render_icon(context, graphicsContext.platformContext()->cr(), icon.get(), rect.x(), rect.y());
1152 return true;
1153}
1154
1155static bool paintEntryIcon(RenderThemePart themePart, const char* iconName, GraphicsContext& graphicsContext, const IntRect& rect, GtkTextDirection direction, GtkStateFlags state)
1156{
1157 GRefPtr<GtkStyleContext> parentContext = createStyleContext(Entry);
1158 GRefPtr<GtkStyleContext> context = createStyleContext(themePart, parentContext.get());
1159 gtk_style_context_set_direction(context.get(), direction);
1160 gtk_style_context_set_state(context.get(), state);
1161 return paintIcon(context.get(), graphicsContext, rect, iconName);
1162}
1163
1164static IntRect centerRectVerticallyInParentInputElement(const RenderObject& renderObject, const IntRect& rect)
1165{
1166 if (!renderObject.node())
1167 return IntRect();
1168
1169 // Get the renderer of <input> element.
1170 Node* input = renderObject.node()->shadowHost();
1171 if (!input)
1172 input = renderObject.node();
1173 if (!is<RenderBox>(*input->renderer()))
1174 return IntRect();
1175
1176 // If possible center the y-coordinate of the rect vertically in the parent input element.
1177 // We also add one pixel here to ensure that the y coordinate is rounded up for box heights
1178 // that are even, which looks in relation to the box text.
1179 IntRect inputContentBox = downcast<RenderBox>(*input->renderer()).absoluteContentBox();
1180
1181 // Make sure the scaled decoration stays square and will fit in its parent's box.
1182 int iconSize = std::min(inputContentBox.width(), std::min(inputContentBox.height(), rect.height()));
1183 IntRect scaledRect(rect.x(), inputContentBox.y() + (inputContentBox.height() - iconSize + 1) / 2, iconSize, iconSize);
1184 return scaledRect;
1185}
1186
1187bool RenderThemeGtk::paintSearchFieldResultsDecorationPart(const RenderBox& renderObject, const PaintInfo& paintInfo, const IntRect& rect)
1188{
1189 IntRect iconRect = centerRectVerticallyInParentInputElement(renderObject, rect);
1190 if (iconRect.isEmpty())
1191 return true;
1192
1193 return !paintEntryIcon(EntryIconLeft, "edit-find-symbolic", paintInfo.context(), iconRect, gtkTextDirection(renderObject.style().direction()),
1194 gtkIconStateFlags(this, renderObject));
1195}
1196
1197bool RenderThemeGtk::paintSearchFieldCancelButton(const RenderBox& renderObject, const PaintInfo& paintInfo, const IntRect& rect)
1198{
1199 IntRect iconRect = centerRectVerticallyInParentInputElement(renderObject, rect);
1200 if (iconRect.isEmpty())
1201 return true;
1202
1203 return !paintEntryIcon(EntryIconRight, "edit-clear-symbolic", paintInfo.context(), iconRect, gtkTextDirection(renderObject.style().direction()),
1204 gtkIconStateFlags(this, renderObject));
1205}
1206#endif // GTK_CHECK_VERSION(3, 20, 0)
1207
1208void RenderThemeGtk::adjustSearchFieldStyle(StyleResolver&, RenderStyle& style, const Element*) const
1209{
1210 // We cannot give a proper rendering when border radius is active, unfortunately.
1211 style.resetBorderRadius();
1212 style.setLineHeight(RenderStyle::initialLineHeight());
1213}
1214
1215bool RenderThemeGtk::paintSearchField(const RenderObject& o, const PaintInfo& i, const IntRect& rect)
1216{
1217 return paintTextField(o, i, rect);
1218}
1219
1220bool RenderThemeGtk::shouldHaveCapsLockIndicator(const HTMLInputElement& element) const
1221{
1222 return element.isPasswordField();
1223}
1224
1225void RenderThemeGtk::adjustSliderTrackStyle(StyleResolver&, RenderStyle& style, const Element*) const
1226{
1227 style.setBoxShadow(nullptr);
1228}
1229
1230void RenderThemeGtk::adjustSliderThumbStyle(StyleResolver& styleResolver, RenderStyle& style, const Element* element) const
1231{
1232 RenderTheme::adjustSliderThumbStyle(styleResolver, style, element);
1233 style.setBoxShadow(nullptr);
1234}
1235
1236#if GTK_CHECK_VERSION(3, 20, 0)
1237/*
1238 * GtkScale
1239 *
1240 * scale
1241 * ╰── contents
1242 * ╰── trough
1243 * ├── slider
1244 * ╰── [highlight]
1245 */
1246bool RenderThemeGtk::paintSliderTrack(const RenderObject& renderObject, const PaintInfo& paintInfo, const IntRect& rect)
1247{
1248 ControlPart part = renderObject.style().appearance();
1249 ASSERT(part == SliderHorizontalPart || part == SliderVerticalPart);
1250
1251 auto& sliderWidget = static_cast<RenderThemeSlider&>(RenderThemeWidget::getOrCreate(part == SliderHorizontalPart ? RenderThemeWidget::Type::HorizontalSlider : RenderThemeWidget::Type::VerticalSlider));
1252 auto scaleState = themePartStateFlags(*this, Scale, renderObject);
1253 auto& scale = sliderWidget.scale();
1254 scale.setState(scaleState);
1255 auto& contents = sliderWidget.contents();
1256 auto& trough = sliderWidget.trough();
1257 trough.setState(scaleState);
1258 auto& slider = sliderWidget.slider();
1259 auto& highlight = sliderWidget.highlight();
1260
1261 // The given rectangle is not calculated based on the scale size, but all the margins and paddings are based on it.
1262 IntSize preferredSize = scale.preferredSize();
1263 preferredSize = preferredSize.expandedTo(contents.preferredSize());
1264 preferredSize = preferredSize.expandedTo(trough.preferredSize());
1265 FloatRect trackRect = rect;
1266 if (part == SliderHorizontalPart) {
1267 trackRect.move(0, rect.height() / 2 - (preferredSize.height() / 2));
1268 trackRect.setHeight(preferredSize.height());
1269 } else {
1270 trackRect.move(rect.width() / 2 - (preferredSize.width() / 2), 0);
1271 trackRect.setWidth(preferredSize.width());
1272 }
1273
1274 FloatRect contentsRect;
1275 scale.render(paintInfo.context().platformContext()->cr(), trackRect, &contentsRect);
1276 contents.render(paintInfo.context().platformContext()->cr(), contentsRect, &contentsRect);
1277 // Scale trough defines its size querying slider and highlight.
1278 if (part == SliderHorizontalPart)
1279 contentsRect.setHeight(trough.preferredSize().height() + std::max(slider.preferredSize().height(), highlight.preferredSize().height()));
1280 else
1281 contentsRect.setWidth(trough.preferredSize().width() + std::max(slider.preferredSize().width(), highlight.preferredSize().width()));
1282 FloatRect troughRect = contentsRect;
1283 trough.render(paintInfo.context().platformContext()->cr(), troughRect, &contentsRect);
1284 if (isFocused(renderObject))
1285 trough.renderFocus(paintInfo.context().platformContext()->cr(), troughRect);
1286
1287 LayoutPoint thumbLocation;
1288 if (is<HTMLInputElement>(renderObject.node())) {
1289 auto& input = downcast<HTMLInputElement>(*renderObject.node());
1290 if (auto* element = input.sliderThumbElement())
1291 thumbLocation = element->renderBox()->location();
1292 }
1293
1294 if (part == SliderHorizontalPart) {
1295 if (renderObject.style().direction() == TextDirection::RTL) {
1296 contentsRect.move(thumbLocation.x(), 0);
1297 contentsRect.setWidth(contentsRect.width() - thumbLocation.x());
1298 } else
1299 contentsRect.setWidth(thumbLocation.x());
1300 } else
1301 contentsRect.setHeight(thumbLocation.y());
1302 highlight.render(paintInfo.context().platformContext()->cr(), contentsRect);
1303
1304 return false;
1305}
1306
1307void RenderThemeGtk::adjustSliderThumbSize(RenderStyle& style, const Element*) const
1308{
1309 ControlPart part = style.appearance();
1310 if (part != SliderThumbHorizontalPart && part != SliderThumbVerticalPart)
1311 return;
1312
1313 auto& sliderWidget = static_cast<RenderThemeSlider&>(RenderThemeWidget::getOrCreate(part == SliderThumbHorizontalPart ? RenderThemeWidget::Type::HorizontalSlider : RenderThemeWidget::Type::VerticalSlider));
1314 sliderWidget.scale().setState(GTK_STATE_FLAG_NORMAL);
1315 sliderWidget.trough().setState(GTK_STATE_FLAG_NORMAL);
1316
1317 IntSize preferredSize = sliderWidget.scale().preferredSize();
1318 preferredSize = preferredSize.expandedTo(sliderWidget.contents().preferredSize());
1319 preferredSize = preferredSize.expandedTo(sliderWidget.trough().preferredSize());
1320 preferredSize = preferredSize.expandedTo(sliderWidget.slider().preferredSize());
1321 if (part == SliderThumbHorizontalPart) {
1322 style.setWidth(Length(preferredSize.width(), Fixed));
1323 style.setHeight(Length(preferredSize.height(), Fixed));
1324 return;
1325 }
1326 ASSERT(part == SliderThumbVerticalPart);
1327 style.setWidth(Length(preferredSize.height(), Fixed));
1328 style.setHeight(Length(preferredSize.width(), Fixed));
1329}
1330
1331bool RenderThemeGtk::paintSliderThumb(const RenderObject& renderObject, const PaintInfo& paintInfo, const IntRect& rect)
1332{
1333 ControlPart part = renderObject.style().appearance();
1334 ASSERT(part == SliderThumbHorizontalPart || part == SliderThumbVerticalPart);
1335
1336 auto& sliderWidget = static_cast<RenderThemeSlider&>(RenderThemeWidget::getOrCreate(part == SliderThumbHorizontalPart ? RenderThemeWidget::Type::HorizontalSlider : RenderThemeWidget::Type::VerticalSlider));
1337 auto scaleState = themePartStateFlags(*this, Scale, renderObject);
1338 auto& scale = sliderWidget.scale();
1339 scale.setState(scaleState);
1340 auto& contents = sliderWidget.contents();
1341 auto& trough = sliderWidget.trough();
1342 trough.setState(scaleState);
1343 auto& slider = sliderWidget.slider();
1344 slider.setState(themePartStateFlags(*this, ScaleSlider, renderObject));
1345 auto& highlight = sliderWidget.highlight();
1346
1347 GtkBorder scaleContentsBox = scale.contentsBox();
1348 GtkBorder contentsContentsBox = contents.contentsBox();
1349 GtkBorder troughContentsBox = trough.contentsBox();
1350 GtkBorder padding;
1351 padding.left = scaleContentsBox.left + contentsContentsBox.left + troughContentsBox.left;
1352 padding.right = scaleContentsBox.right + contentsContentsBox.right + troughContentsBox.right;
1353 padding.top = scaleContentsBox.top + contentsContentsBox.top + troughContentsBox.top;
1354 padding.bottom = scaleContentsBox.bottom + contentsContentsBox.bottom + troughContentsBox.bottom;
1355
1356 // Scale trough defines its size querying slider and highlight.
1357 int troughHeight = trough.preferredSize().height() + std::max(slider.preferredSize().height(), highlight.preferredSize().height());
1358 IntRect sliderRect(rect.location(), IntSize(troughHeight, troughHeight));
1359 sliderRect.move(padding.left, padding.top);
1360 sliderRect.contract(padding.left + padding.right, padding.top + padding.bottom);
1361 slider.render(paintInfo.context().platformContext()->cr(), sliderRect);
1362 return false;
1363}
1364#else
1365bool RenderThemeGtk::paintSliderTrack(const RenderObject& renderObject, const PaintInfo& paintInfo, const IntRect& rect)
1366{
1367 ControlPart part = renderObject.style().appearance();
1368 ASSERT(part == SliderHorizontalPart || part == SliderVerticalPart);
1369
1370 GRefPtr<GtkStyleContext> parentContext = createStyleContext(Scale);
1371 gtk_style_context_add_class(parentContext.get(), part == SliderHorizontalPart ? GTK_STYLE_CLASS_HORIZONTAL : GTK_STYLE_CLASS_VERTICAL);
1372 GRefPtr<GtkStyleContext> context = createStyleContext(ScaleTrough, parentContext.get());
1373 gtk_style_context_set_direction(context.get(), gtkTextDirection(renderObject.style().direction()));
1374
1375 if (!isEnabled(renderObject))
1376 gtk_style_context_set_state(context.get(), GTK_STATE_FLAG_INSENSITIVE);
1377
1378 IntRect sliderRect = rect;
1379 // GTK+ uses the slider thumb size and margins to calculate the trough size, but in WebKit we render the thumb and
1380 // the slider track separately and the track rectangle we receive here can't be used to apply the GTK+ CSS sizes
1381 // and margins. So we use a maximum fixed size for the trough to match at least Adwaita, but that should look
1382 // good in other themes as well.
1383 static const int sliderSize = 4;
1384
1385 if (part == SliderHorizontalPart) {
1386 sliderRect.setHeight(std::min(rect.height(), sliderSize));
1387 sliderRect.move(0, (rect.height() - sliderRect.height()) / 2);
1388 } else {
1389 sliderRect.setWidth(std::min(rect.width(), sliderSize));
1390 sliderRect.move((rect.width() - sliderRect.width()) / 2, 0);
1391 }
1392
1393 gtk_render_background(context.get(), paintInfo.context().platformContext()->cr(), sliderRect.x(), sliderRect.y(), sliderRect.width(), sliderRect.height());
1394 gtk_render_frame(context.get(), paintInfo.context().platformContext()->cr(), sliderRect.x(), sliderRect.y(), sliderRect.width(), sliderRect.height());
1395
1396 if (isFocused(renderObject)) {
1397 gint focusWidth, focusPad;
1398 gtk_style_context_get_style(context.get(), "focus-line-width", &focusWidth, "focus-padding", &focusPad, nullptr);
1399 IntRect focusRect(sliderRect);
1400 focusRect.inflate(focusWidth + focusPad);
1401 gtk_render_focus(context.get(), paintInfo.context().platformContext()->cr(), focusRect.x(), focusRect.y(), focusRect.width(), focusRect.height());
1402 }
1403
1404 return false;
1405}
1406
1407void RenderThemeGtk::adjustSliderThumbSize(RenderStyle& style, const Element*) const
1408{
1409 ControlPart part = style.appearance();
1410 if (part != SliderThumbHorizontalPart && part != SliderThumbVerticalPart)
1411 return;
1412
1413 GRefPtr<GtkStyleContext> context = createStyleContext(Scale);
1414 gint sliderWidth, sliderLength;
1415 gtk_style_context_get_style(context.get(), "slider-width", &sliderWidth, "slider-length", &sliderLength, nullptr);
1416
1417 if (part == SliderThumbHorizontalPart) {
1418 style.setWidth(Length(sliderLength, Fixed));
1419 style.setHeight(Length(sliderWidth, Fixed));
1420 return;
1421 }
1422 ASSERT(part == SliderThumbVerticalPart);
1423 style.setWidth(Length(sliderWidth, Fixed));
1424 style.setHeight(Length(sliderLength, Fixed));
1425}
1426
1427bool RenderThemeGtk::paintSliderThumb(const RenderObject& renderObject, const PaintInfo& paintInfo, const IntRect& rect)
1428{
1429 ControlPart part = renderObject.style().appearance();
1430 ASSERT(part == SliderThumbHorizontalPart || part == SliderThumbVerticalPart);
1431
1432 // FIXME: The entire slider is too wide, stretching the thumb into an oval rather than a circle.
1433 GRefPtr<GtkStyleContext> parentContext = createStyleContext(Scale);
1434 gtk_style_context_add_class(parentContext.get(), part == SliderThumbHorizontalPart ? GTK_STYLE_CLASS_HORIZONTAL : GTK_STYLE_CLASS_VERTICAL);
1435 GRefPtr<GtkStyleContext> troughContext = createStyleContext(ScaleTrough, parentContext.get());
1436 GRefPtr<GtkStyleContext> context = createStyleContext(ScaleSlider, troughContext.get());
1437 gtk_style_context_set_direction(context.get(), gtkTextDirection(renderObject.style().direction()));
1438
1439 guint flags = 0;
1440 if (!isEnabled(renderObject))
1441 flags |= GTK_STATE_FLAG_INSENSITIVE;
1442 else if (isHovered(renderObject))
1443 flags |= GTK_STATE_FLAG_PRELIGHT;
1444 if (isPressed(renderObject))
1445 flags |= GTK_STATE_FLAG_ACTIVE;
1446 gtk_style_context_set_state(context.get(), static_cast<GtkStateFlags>(flags));
1447
1448 gtk_render_slider(context.get(), paintInfo.context().platformContext()->cr(), rect.x(), rect.y(), rect.width(), rect.height(),
1449 part == SliderThumbHorizontalPart ? GTK_ORIENTATION_HORIZONTAL : GTK_ORIENTATION_VERTICAL);
1450
1451 return false;
1452}
1453#endif
1454
1455#if GTK_CHECK_VERSION(3, 20, 0)
1456IntRect RenderThemeGtk::progressBarRectForBounds(const RenderObject& renderObject, const IntRect& bounds) const
1457{
1458 const auto& renderProgress = downcast<RenderProgress>(renderObject);
1459 auto& progressBarWidget = static_cast<RenderThemeProgressBar&>(RenderThemeWidget::getOrCreate(renderProgress.isDeterminate() ? RenderThemeProgressBar::Type::ProgressBar : RenderThemeProgressBar::Type::IndeterminateProgressBar));
1460 IntSize preferredSize = progressBarWidget.progressBar().preferredSize();
1461 preferredSize = preferredSize.expandedTo(progressBarWidget.trough().preferredSize());
1462 preferredSize = preferredSize.expandedTo(progressBarWidget.progress().preferredSize());
1463 return IntRect(bounds.x(), bounds.y(), bounds.width(), preferredSize.height());
1464}
1465
1466bool RenderThemeGtk::paintProgressBar(const RenderObject& renderObject, const PaintInfo& paintInfo, const IntRect& rect)
1467{
1468 if (!renderObject.isProgress())
1469 return true;
1470
1471 const auto& renderProgress = downcast<RenderProgress>(renderObject);
1472 auto& progressBarWidget = static_cast<RenderThemeProgressBar&>(RenderThemeWidget::getOrCreate(renderProgress.isDeterminate() ? RenderThemeProgressBar::Type::ProgressBar : RenderThemeProgressBar::Type::IndeterminateProgressBar));
1473 progressBarWidget.progressBar().render(paintInfo.context().platformContext()->cr(), rect);
1474 progressBarWidget.trough().render(paintInfo.context().platformContext()->cr(), rect);
1475 progressBarWidget.progress().render(paintInfo.context().platformContext()->cr(), calculateProgressRect(renderObject, rect));
1476 return false;
1477}
1478#else
1479IntRect RenderThemeGtk::progressBarRectForBounds(const RenderObject&, const IntRect& bounds) const
1480{
1481 return bounds;
1482}
1483
1484bool RenderThemeGtk::paintProgressBar(const RenderObject& renderObject, const PaintInfo& paintInfo, const IntRect& rect)
1485{
1486 if (!renderObject.isProgress())
1487 return true;
1488
1489 GRefPtr<GtkStyleContext> parentContext = createStyleContext(ProgressBar);
1490 GRefPtr<GtkStyleContext> troughContext = createStyleContext(ProgressBarTrough, parentContext.get());
1491 GRefPtr<GtkStyleContext> context = createStyleContext(ProgressBarProgress, troughContext.get());
1492
1493 gtk_render_background(troughContext.get(), paintInfo.context().platformContext()->cr(), rect.x(), rect.y(), rect.width(), rect.height());
1494 gtk_render_frame(troughContext.get(), paintInfo.context().platformContext()->cr(), rect.x(), rect.y(), rect.width(), rect.height());
1495
1496 gtk_style_context_set_state(context.get(), static_cast<GtkStateFlags>(0));
1497
1498 GtkBorder padding;
1499 gtk_style_context_get_padding(context.get(), gtk_style_context_get_state(context.get()), &padding);
1500 IntRect progressRect(
1501 rect.x() + padding.left,
1502 rect.y() + padding.top,
1503 rect.width() - (padding.left + padding.right),
1504 rect.height() - (padding.top + padding.bottom));
1505 progressRect = RenderThemeGtk::calculateProgressRect(renderObject, progressRect);
1506
1507 if (!progressRect.isEmpty()) {
1508#if GTK_CHECK_VERSION(3, 13, 7)
1509 gtk_render_background(context.get(), paintInfo.context().platformContext()->cr(), progressRect.x(), progressRect.y(), progressRect.width(), progressRect.height());
1510 gtk_render_frame(context.get(), paintInfo.context().platformContext()->cr(), progressRect.x(), progressRect.y(), progressRect.width(), progressRect.height());
1511#else
1512 gtk_render_activity(context.get(), paintInfo.context().platformContext()->cr(), progressRect.x(), progressRect.y(), progressRect.width(), progressRect.height());
1513#endif
1514 }
1515
1516 return false;
1517}
1518#endif // GTK_CHECK_VERSION(3, 20, 0)
1519
1520#if GTK_CHECK_VERSION(3, 20, 0)
1521RenderTheme::InnerSpinButtonLayout RenderThemeGtk::innerSpinButtonLayout(const RenderObject& renderObject) const
1522{
1523 return renderObject.style().direction() == TextDirection::RTL ? InnerSpinButtonLayout::HorizontalUpLeft : InnerSpinButtonLayout::HorizontalUpRight;
1524}
1525
1526void RenderThemeGtk::adjustInnerSpinButtonStyle(StyleResolver&, RenderStyle& style, const Element*) const
1527{
1528 style.setWidth(Length(spinButtonSize().width(), Fixed));
1529 style.setHeight(Length(spinButtonSize().height(), Fixed));
1530}
1531
1532bool RenderThemeGtk::paintInnerSpinButton(const RenderObject& renderObject, const PaintInfo& paintInfo, const IntRect& rect)
1533{
1534 auto& spinButtonWidget = static_cast<RenderThemeSpinButton&>(RenderThemeWidget::getOrCreate(RenderThemeWidget::Type::SpinButton));
1535 auto spinButtonState = themePartStateFlags(*this, SpinButton, renderObject);
1536 spinButtonWidget.spinButton().setState(spinButtonState);
1537 spinButtonWidget.entry().setState(spinButtonState);
1538 auto& up = spinButtonWidget.up();
1539 up.setState(themePartStateFlags(*this, SpinButtonUpButton, renderObject));
1540 auto& down = spinButtonWidget.down();
1541 down.setState(themePartStateFlags(*this, SpinButtonDownButton, renderObject));
1542
1543 IntRect iconRect = rect;
1544 iconRect.setWidth(iconRect.width() / 2);
1545 if (renderObject.style().direction() == TextDirection::RTL)
1546 up.render(paintInfo.context().platformContext()->cr(), iconRect);
1547 else
1548 down.render(paintInfo.context().platformContext()->cr(), iconRect);
1549 iconRect.move(iconRect.width(), 0);
1550 if (renderObject.style().direction() == TextDirection::RTL)
1551 down.render(paintInfo.context().platformContext()->cr(), iconRect);
1552 else
1553 up.render(paintInfo.context().platformContext()->cr(), iconRect);
1554
1555 return false;
1556}
1557#else
1558RenderTheme::InnerSpinButtonLayout RenderThemeGtk::innerSpinButtonLayout(const RenderObject&) const
1559{
1560 return InnerSpinButtonLayout::Vertical;
1561}
1562static gint spinButtonArrowSize(GtkStyleContext* context)
1563{
1564 PangoFontDescription* fontDescription;
1565 gtk_style_context_get(context, gtk_style_context_get_state(context), "font", &fontDescription, nullptr);
1566 gint fontSize = pango_font_description_get_size(fontDescription);
1567 gint arrowSize = std::max(PANGO_PIXELS(fontSize), minSpinButtonArrowSize);
1568 pango_font_description_free(fontDescription);
1569
1570 return arrowSize - arrowSize % 2; // Force even.
1571}
1572
1573void RenderThemeGtk::adjustInnerSpinButtonStyle(StyleResolver&, RenderStyle& style, const Element*) const
1574{
1575 GRefPtr<GtkStyleContext> context = createStyleContext(SpinButton);
1576
1577 GtkBorder padding;
1578 gtk_style_context_get_padding(context.get(), gtk_style_context_get_state(context.get()), &padding);
1579
1580 int width = spinButtonArrowSize(context.get()) + padding.left + padding.right;
1581 style.setWidth(Length(width, Fixed));
1582 style.setMinWidth(Length(width, Fixed));
1583}
1584
1585static void paintSpinArrowButton(RenderTheme* theme, GtkStyleContext* parentContext, const RenderObject& renderObject, const PaintInfo& paintInfo, const IntRect& rect, GtkArrowType arrowType)
1586{
1587 ASSERT(arrowType == GTK_ARROW_UP || arrowType == GTK_ARROW_DOWN);
1588
1589 GRefPtr<GtkStyleContext> context = createStyleContext(arrowType == GTK_ARROW_UP ? SpinButtonUpButton : SpinButtonDownButton, parentContext);
1590 GtkTextDirection direction = gtk_style_context_get_direction(context.get());
1591 guint state = static_cast<guint>(gtk_style_context_get_state(context.get()));
1592 if (!(state & GTK_STATE_FLAG_INSENSITIVE)) {
1593 if (theme->isPressed(renderObject)) {
1594 if ((arrowType == GTK_ARROW_UP && theme->isSpinUpButtonPartPressed(renderObject))
1595 || (arrowType == GTK_ARROW_DOWN && !theme->isSpinUpButtonPartPressed(renderObject)))
1596 state |= GTK_STATE_FLAG_ACTIVE;
1597 } else if (theme->isHovered(renderObject)) {
1598 if ((arrowType == GTK_ARROW_UP && theme->isSpinUpButtonPartHovered(renderObject))
1599 || (arrowType == GTK_ARROW_DOWN && !theme->isSpinUpButtonPartHovered(renderObject)))
1600 state |= GTK_STATE_FLAG_PRELIGHT;
1601 }
1602 }
1603 gtk_style_context_set_state(context.get(), static_cast<GtkStateFlags>(state));
1604
1605 // Paint button.
1606 IntRect buttonRect(rect);
1607 guint junction = gtk_style_context_get_junction_sides(context.get());
1608 if (arrowType == GTK_ARROW_UP)
1609 junction |= GTK_JUNCTION_BOTTOM;
1610 else {
1611 junction |= GTK_JUNCTION_TOP;
1612 buttonRect.move(0, rect.height() / 2);
1613 }
1614 buttonRect.setHeight(rect.height() / 2);
1615 gtk_style_context_set_junction_sides(context.get(), static_cast<GtkJunctionSides>(junction));
1616
1617 gtk_render_background(context.get(), paintInfo.context().platformContext()->cr(), buttonRect.x(), buttonRect.y(), buttonRect.width(), buttonRect.height());
1618 gtk_render_frame(context.get(), paintInfo.context().platformContext()->cr(), buttonRect.x(), buttonRect.y(), buttonRect.width(), buttonRect.height());
1619
1620 // Paint arrow centered inside button.
1621 // This code is based on gtkspinbutton.c code.
1622 IntRect arrowRect;
1623 gdouble angle;
1624 if (arrowType == GTK_ARROW_UP) {
1625 angle = 0;
1626 arrowRect.setY(rect.y());
1627 arrowRect.setHeight(rect.height() / 2 - 2);
1628 } else {
1629 angle = G_PI;
1630 arrowRect.setY(rect.y() + buttonRect.y());
1631 arrowRect.setHeight(rect.height() - arrowRect.y() - 2);
1632 }
1633 arrowRect.setWidth(rect.width() - 3);
1634 if (direction == GTK_TEXT_DIR_LTR)
1635 arrowRect.setX(rect.x() + 1);
1636 else
1637 arrowRect.setX(rect.x() + 2);
1638
1639 gint width = arrowRect.width() / 2;
1640 width -= width % 2 - 1; // Force odd.
1641 gint height = (width + 1) / 2;
1642
1643 arrowRect.move((arrowRect.width() - width) / 2, (arrowRect.height() - height) / 2);
1644 gtk_render_arrow(context.get(), paintInfo.context().platformContext()->cr(), angle, arrowRect.x(), arrowRect.y(), width);
1645}
1646
1647bool RenderThemeGtk::paintInnerSpinButton(const RenderObject& renderObject, const PaintInfo& paintInfo, const IntRect& rect)
1648{
1649 GRefPtr<GtkStyleContext> context = createStyleContext(SpinButton);
1650 gtk_style_context_set_direction(context.get(), gtkTextDirection(renderObject.style().direction()));
1651
1652 guint flags = 0;
1653 if (!isEnabled(renderObject) || isReadOnlyControl(renderObject))
1654 flags |= GTK_STATE_FLAG_INSENSITIVE;
1655 else if (isFocused(renderObject))
1656 flags |= GTK_STATE_FLAG_FOCUSED;
1657 gtk_style_context_set_state(context.get(), static_cast<GtkStateFlags>(flags));
1658
1659 paintSpinArrowButton(this, context.get(), renderObject, paintInfo, rect, GTK_ARROW_UP);
1660 paintSpinArrowButton(this, context.get(), renderObject, paintInfo, rect, GTK_ARROW_DOWN);
1661
1662 return false;
1663}
1664#endif // GTK_CHECK_VERSION(3, 20, 0)
1665
1666Seconds RenderThemeGtk::caretBlinkInterval() const
1667{
1668 GtkSettings* settings = gtk_settings_get_default();
1669
1670 gboolean shouldBlink;
1671 gint time;
1672
1673 g_object_get(settings, "gtk-cursor-blink", &shouldBlink, "gtk-cursor-blink-time", &time, nullptr);
1674
1675 if (!shouldBlink)
1676 return 0_s;
1677
1678 return 500_us * time;
1679}
1680
1681enum StyleColorType { StyleColorBackground, StyleColorForeground };
1682
1683#if GTK_CHECK_VERSION(3, 20, 0)
1684static Color styleColor(RenderThemePart themePart, GtkStateFlags state, StyleColorType colorType)
1685{
1686 RenderThemeGadget* gadget = nullptr;
1687 switch (themePart) {
1688 default:
1689 ASSERT_NOT_REACHED();
1690 FALLTHROUGH;
1691 case Entry:
1692 gadget = &static_cast<RenderThemeEntry&>(RenderThemeWidget::getOrCreate(RenderThemeWidget::Type::Entry)).entry();
1693 break;
1694 case EntrySelection:
1695 gadget = static_cast<RenderThemeEntry&>(RenderThemeWidget::getOrCreate(RenderThemeWidget::Type::SelectedEntry)).selection();
1696 break;
1697 case ListBox:
1698 gadget = &static_cast<RenderThemeListView&>(RenderThemeWidget::getOrCreate(RenderThemeWidget::Type::ListView)).treeview();
1699 break;
1700 case Button:
1701 gadget = &static_cast<RenderThemeButton&>(RenderThemeWidget::getOrCreate(RenderThemeWidget::Type::Button)).button();
1702 break;
1703 case Window:
1704 gadget = &static_cast<RenderThemeWindow&>(RenderThemeWidget::getOrCreate(RenderThemeWidget::Type::Window)).window();
1705 break;
1706 }
1707
1708 ASSERT(gadget);
1709 gadget->setState(state);
1710 return colorType == StyleColorBackground ? gadget->backgroundColor() : gadget->color();
1711}
1712#else
1713static Color styleColor(RenderThemePart themePart, GtkStateFlags state, StyleColorType colorType)
1714{
1715 GRefPtr<GtkStyleContext> context = createStyleContext(themePart);
1716 gtk_style_context_set_state(context.get(), state);
1717
1718 GdkRGBA gdkRGBAColor;
1719 if (colorType == StyleColorBackground)
1720 gtk_style_context_get_background_color(context.get(), state, &gdkRGBAColor);
1721 else
1722 gtk_style_context_get_color(context.get(), state, &gdkRGBAColor);
1723 return gdkRGBAColor;
1724}
1725#endif // GTK_CHECK_VERSION(3, 20, 0)
1726
1727Color RenderThemeGtk::platformActiveSelectionBackgroundColor(OptionSet<StyleColor::Options>) const
1728{
1729 return styleColor(EntrySelection, static_cast<GtkStateFlags>(GTK_STATE_FLAG_SELECTED | GTK_STATE_FLAG_FOCUSED), StyleColorBackground);
1730}
1731
1732Color RenderThemeGtk::platformInactiveSelectionBackgroundColor(OptionSet<StyleColor::Options>) const
1733{
1734 return styleColor(EntrySelection, GTK_STATE_FLAG_SELECTED, StyleColorBackground);
1735}
1736
1737Color RenderThemeGtk::platformActiveSelectionForegroundColor(OptionSet<StyleColor::Options>) const
1738{
1739 return styleColor(EntrySelection, static_cast<GtkStateFlags>(GTK_STATE_FLAG_SELECTED | GTK_STATE_FLAG_FOCUSED), StyleColorForeground);
1740}
1741
1742Color RenderThemeGtk::platformInactiveSelectionForegroundColor(OptionSet<StyleColor::Options>) const
1743{
1744 return styleColor(EntrySelection, GTK_STATE_FLAG_SELECTED, StyleColorForeground);
1745}
1746
1747Color RenderThemeGtk::platformActiveListBoxSelectionBackgroundColor(OptionSet<StyleColor::Options>) const
1748{
1749 return styleColor(ListBox, static_cast<GtkStateFlags>(GTK_STATE_FLAG_SELECTED | GTK_STATE_FLAG_FOCUSED), StyleColorBackground);
1750}
1751
1752Color RenderThemeGtk::platformInactiveListBoxSelectionBackgroundColor(OptionSet<StyleColor::Options>) const
1753{
1754 return styleColor(ListBox, GTK_STATE_FLAG_SELECTED, StyleColorBackground);
1755}
1756
1757Color RenderThemeGtk::platformActiveListBoxSelectionForegroundColor(OptionSet<StyleColor::Options>) const
1758{
1759 return styleColor(ListBox, static_cast<GtkStateFlags>(GTK_STATE_FLAG_SELECTED | GTK_STATE_FLAG_FOCUSED), StyleColorForeground);
1760}
1761
1762Color RenderThemeGtk::platformInactiveListBoxSelectionForegroundColor(OptionSet<StyleColor::Options>) const
1763{
1764 return styleColor(ListBox, GTK_STATE_FLAG_SELECTED, StyleColorForeground);
1765}
1766
1767Color RenderThemeGtk::disabledTextColor(const Color&, const Color&) const
1768{
1769 return styleColor(Entry, GTK_STATE_FLAG_INSENSITIVE, StyleColorForeground);
1770}
1771
1772Color RenderThemeGtk::systemColor(CSSValueID cssValueId, OptionSet<StyleColor::Options> options) const
1773{
1774 switch (cssValueId) {
1775 case CSSValueButtontext:
1776 return styleColor(Button, GTK_STATE_FLAG_ACTIVE, StyleColorForeground);
1777 case CSSValueCaptiontext:
1778 return styleColor(Entry, GTK_STATE_FLAG_ACTIVE, StyleColorForeground);
1779 case CSSValueText:
1780 return styleColor(Entry, GTK_STATE_FLAG_ACTIVE, StyleColorForeground);
1781 case CSSValueGraytext:
1782 return styleColor(Entry, GTK_STATE_FLAG_INSENSITIVE, StyleColorForeground);
1783 case CSSValueWebkitControlBackground:
1784 return styleColor(Entry, GTK_STATE_FLAG_ACTIVE, StyleColorBackground);
1785#if GTK_CHECK_VERSION(3, 20, 0)
1786 case CSSValueWindow: {
1787 // Only get window color from the theme in dark mode.
1788 gboolean preferDarkTheme = FALSE;
1789 if (auto* settings = gtk_settings_get_default())
1790 g_object_get(settings, "gtk-application-prefer-dark-theme", &preferDarkTheme, nullptr);
1791 if (preferDarkTheme)
1792 return styleColor(Window, GTK_STATE_FLAG_ACTIVE, StyleColorBackground);
1793 break;
1794 }
1795#endif
1796 default:
1797 break;
1798 }
1799
1800 return RenderTheme::systemColor(cssValueId, options);
1801}
1802
1803void RenderThemeGtk::platformColorsDidChange()
1804{
1805 RenderTheme::platformColorsDidChange();
1806}
1807
1808#if ENABLE(VIDEO)
1809String RenderThemeGtk::extraMediaControlsStyleSheet()
1810{
1811 return String(mediaControlsGtkUserAgentStyleSheet, sizeof(mediaControlsGtkUserAgentStyleSheet));
1812}
1813
1814#if ENABLE(FULLSCREEN_API)
1815String RenderThemeGtk::extraFullScreenStyleSheet()
1816{
1817 return String();
1818}
1819#endif
1820
1821#if GTK_CHECK_VERSION(3, 20, 0)
1822bool RenderThemeGtk::paintMediaButton(const RenderObject& renderObject, GraphicsContext& graphicsContext, const IntRect& rect, const char* iconName)
1823{
1824 auto& iconWidget = static_cast<RenderThemeIcon&>(RenderThemeWidget::getOrCreate(RenderThemeWidget::Type::Icon));
1825 auto& icon = static_cast<RenderThemeIconGadget&>(iconWidget.icon());
1826 icon.setState(themePartStateFlags(*this, MediaButton, renderObject));
1827 icon.setIconSize(RenderThemeIconGadget::IconSizeGtk::Menu);
1828 icon.setIconName(iconName);
1829 return !icon.render(graphicsContext.platformContext()->cr(), rect);
1830}
1831#else
1832bool RenderThemeGtk::paintMediaButton(const RenderObject& renderObject, GraphicsContext& graphicsContext, const IntRect& rect, const char* iconName)
1833{
1834 GRefPtr<GtkStyleContext> context = createStyleContext(MediaButton);
1835 gtk_style_context_set_direction(context.get(), gtkTextDirection(renderObject.style().direction()));
1836 gtk_style_context_set_state(context.get(), gtkIconStateFlags(this, renderObject));
1837 static const unsigned mediaIconSize = 16;
1838 IntRect iconRect(rect.x() + (rect.width() - mediaIconSize) / 2, rect.y() + (rect.height() - mediaIconSize) / 2, mediaIconSize, mediaIconSize);
1839 return !paintIcon(context.get(), graphicsContext, iconRect, iconName);
1840}
1841#endif // GTK_CHECK_VERSION(3, 20, 0)
1842
1843bool RenderThemeGtk::hasOwnDisabledStateHandlingFor(ControlPart part) const
1844{
1845 return (part != MediaMuteButtonPart);
1846}
1847
1848bool RenderThemeGtk::paintMediaFullscreenButton(const RenderObject& renderObject, const PaintInfo& paintInfo, const IntRect& rect)
1849{
1850 return paintMediaButton(renderObject, paintInfo.context(), rect, "view-fullscreen-symbolic");
1851}
1852
1853bool RenderThemeGtk::paintMediaMuteButton(const RenderObject& renderObject, const PaintInfo& paintInfo, const IntRect& rect)
1854{
1855 Node* node = renderObject.node();
1856 if (!node)
1857 return true;
1858 Node* mediaNode = node->shadowHost();
1859 if (!is<HTMLMediaElement>(mediaNode))
1860 return true;
1861
1862 HTMLMediaElement* mediaElement = downcast<HTMLMediaElement>(mediaNode);
1863 return paintMediaButton(renderObject, paintInfo.context(), rect, mediaElement->muted() ? "audio-volume-muted-symbolic" : "audio-volume-high-symbolic");
1864}
1865
1866bool RenderThemeGtk::paintMediaPlayButton(const RenderObject& renderObject, const PaintInfo& paintInfo, const IntRect& rect)
1867{
1868 Node* node = renderObject.node();
1869 if (!node)
1870 return true;
1871 if (!nodeHasPseudo(*node, "-webkit-media-controls-play-button"))
1872 return true;
1873
1874 return paintMediaButton(renderObject, paintInfo.context(), rect, nodeHasClass(node, "paused") ? "media-playback-start-symbolic" : "media-playback-pause-symbolic");
1875}
1876
1877bool RenderThemeGtk::paintMediaSeekBackButton(const RenderObject& renderObject, const PaintInfo& paintInfo, const IntRect& rect)
1878{
1879 return paintMediaButton(renderObject, paintInfo.context(), rect, "media-seek-backward-symbolic");
1880}
1881
1882bool RenderThemeGtk::paintMediaSeekForwardButton(const RenderObject& renderObject, const PaintInfo& paintInfo, const IntRect& rect)
1883{
1884 return paintMediaButton(renderObject, paintInfo.context(), rect, "media-seek-forward-symbolic");
1885}
1886
1887#if ENABLE(VIDEO_TRACK)
1888bool RenderThemeGtk::paintMediaToggleClosedCaptionsButton(const RenderObject& renderObject, const PaintInfo& paintInfo, const IntRect& rect)
1889{
1890 return paintMediaButton(renderObject, paintInfo.context(), rect, "media-view-subtitles-symbolic");
1891}
1892#endif
1893
1894static FloatRoundedRect::Radii borderRadiiFromStyle(const RenderStyle& style)
1895{
1896 return FloatRoundedRect::Radii(
1897 IntSize(style.borderTopLeftRadius().width.intValue(), style.borderTopLeftRadius().height.intValue()),
1898 IntSize(style.borderTopRightRadius().width.intValue(), style.borderTopRightRadius().height.intValue()),
1899 IntSize(style.borderBottomLeftRadius().width.intValue(), style.borderBottomLeftRadius().height.intValue()),
1900 IntSize(style.borderBottomRightRadius().width.intValue(), style.borderBottomRightRadius().height.intValue()));
1901}
1902
1903bool RenderThemeGtk::paintMediaSliderTrack(const RenderObject& o, const PaintInfo& paintInfo, const IntRect& r)
1904{
1905 auto mediaElement = parentMediaElement(o);
1906 if (!mediaElement)
1907 return true;
1908
1909 GraphicsContext& context = paintInfo.context();
1910 context.save();
1911 context.setStrokeStyle(NoStroke);
1912
1913 float mediaDuration = mediaElement->duration();
1914 float totalTrackWidth = r.width();
1915 auto& style = o.style();
1916 RefPtr<TimeRanges> timeRanges = mediaElement->buffered();
1917 for (unsigned index = 0; index < timeRanges->length(); ++index) {
1918 float start = timeRanges->start(index).releaseReturnValue();
1919 float end = timeRanges->end(index).releaseReturnValue();
1920 float startRatio = start / mediaDuration;
1921 float lengthRatio = (end - start) / mediaDuration;
1922 if (!lengthRatio)
1923 continue;
1924
1925 IntRect rangeRect(r);
1926 rangeRect.setWidth(lengthRatio * totalTrackWidth);
1927 if (index)
1928 rangeRect.move(startRatio * totalTrackWidth, 0);
1929 context.fillRoundedRect(FloatRoundedRect(rangeRect, borderRadiiFromStyle(style)), style.visitedDependentColor(CSSPropertyColor));
1930 }
1931
1932 context.restore();
1933 return false;
1934}
1935
1936bool RenderThemeGtk::paintMediaSliderThumb(const RenderObject& o, const PaintInfo& paintInfo, const IntRect& r)
1937{
1938 auto& style = o.style();
1939 paintInfo.context().fillRoundedRect(FloatRoundedRect(r, borderRadiiFromStyle(style)), style.visitedDependentColor(CSSPropertyColor));
1940 return false;
1941}
1942
1943bool RenderThemeGtk::paintMediaVolumeSliderTrack(const RenderObject& renderObject, const PaintInfo& paintInfo, const IntRect& rect)
1944{
1945 auto mediaElement = parentMediaElement(renderObject);
1946 if (!mediaElement)
1947 return true;
1948
1949 float volume = mediaElement->muted() ? 0.0f : mediaElement->volume();
1950 if (!volume)
1951 return true;
1952
1953 GraphicsContext& context = paintInfo.context();
1954 context.save();
1955 context.setStrokeStyle(NoStroke);
1956
1957 int rectHeight = rect.height();
1958 float trackHeight = rectHeight * volume;
1959 auto& style = renderObject.style();
1960 IntRect volumeRect(rect);
1961 volumeRect.move(0, rectHeight - trackHeight);
1962 volumeRect.setHeight(ceil(trackHeight));
1963
1964 context.fillRoundedRect(FloatRoundedRect(volumeRect, borderRadiiFromStyle(style)), style.visitedDependentColor(CSSPropertyColor));
1965 context.restore();
1966
1967 return false;
1968}
1969
1970bool RenderThemeGtk::paintMediaVolumeSliderThumb(const RenderObject& renderObject, const PaintInfo& paintInfo, const IntRect& rect)
1971{
1972 return paintMediaSliderThumb(renderObject, paintInfo, rect);
1973}
1974
1975String RenderThemeGtk::formatMediaControlsCurrentTime(float currentTime, float duration) const
1976{
1977 return formatMediaControlsTime(currentTime) + " / " + formatMediaControlsTime(duration);
1978}
1979
1980bool RenderThemeGtk::paintMediaCurrentTime(const RenderObject&, const PaintInfo&, const IntRect&)
1981{
1982 return false;
1983}
1984#endif
1985
1986void RenderThemeGtk::adjustProgressBarStyle(StyleResolver&, RenderStyle& style, const Element*) const
1987{
1988 style.setBoxShadow(nullptr);
1989}
1990
1991// These values have been copied from RenderThemeChromiumSkia.cpp
1992static const int progressActivityBlocks = 5;
1993static const int progressAnimationFrames = 10;
1994static const Seconds progressAnimationInterval { 125_ms };
1995Seconds RenderThemeGtk::animationRepeatIntervalForProgressBar(RenderProgress&) const
1996{
1997 return progressAnimationInterval;
1998}
1999
2000Seconds RenderThemeGtk::animationDurationForProgressBar(RenderProgress&) const
2001{
2002 return progressAnimationInterval * progressAnimationFrames * 2; // "2" for back and forth;
2003}
2004
2005IntRect RenderThemeGtk::calculateProgressRect(const RenderObject& renderObject, const IntRect& fullBarRect)
2006{
2007 IntRect progressRect(fullBarRect);
2008 const auto& renderProgress = downcast<RenderProgress>(renderObject);
2009 if (renderProgress.isDeterminate()) {
2010 int progressWidth = progressRect.width() * renderProgress.position();
2011 if (renderObject.style().direction() == TextDirection::RTL)
2012 progressRect.setX(progressRect.x() + progressRect.width() - progressWidth);
2013 progressRect.setWidth(progressWidth);
2014 return progressRect;
2015 }
2016
2017 double animationProgress = renderProgress.animationProgress();
2018
2019 // Never let the progress rect shrink smaller than 2 pixels.
2020 int newWidth = std::max(2, progressRect.width() / progressActivityBlocks);
2021 int movableWidth = progressRect.width() - newWidth;
2022 progressRect.setWidth(newWidth);
2023
2024 // We want the first 0.5 units of the animation progress to represent the
2025 // forward motion and the second 0.5 units to represent the backward motion,
2026 // thus we multiply by two here to get the full sweep of the progress bar with
2027 // each direction.
2028 if (animationProgress < 0.5)
2029 progressRect.setX(progressRect.x() + (animationProgress * 2 * movableWidth));
2030 else
2031 progressRect.setX(progressRect.x() + ((1.0 - animationProgress) * 2 * movableWidth));
2032 return progressRect;
2033}
2034
2035String RenderThemeGtk::fileListNameForWidth(const FileList* fileList, const FontCascade& font, int width, bool multipleFilesAllowed) const
2036{
2037 if (width <= 0)
2038 return String();
2039
2040 if (fileList->length() > 1)
2041 return StringTruncator::rightTruncate(multipleFileUploadText(fileList->length()), width, font);
2042
2043 String string;
2044 if (fileList->length())
2045 string = FileSystem::pathGetFileName(fileList->item(0)->path());
2046 else if (multipleFilesAllowed)
2047 string = fileButtonNoFilesSelectedLabel();
2048 else
2049 string = fileButtonNoFileSelectedLabel();
2050
2051 return StringTruncator::centerTruncate(string, width, font);
2052}
2053
2054#if ENABLE(VIDEO)
2055String RenderThemeGtk::mediaControlsScript()
2056{
2057 StringBuilder scriptBuilder;
2058 scriptBuilder.append(mediaControlsLocalizedStringsJavaScript, sizeof(mediaControlsLocalizedStringsJavaScript));
2059 scriptBuilder.append(mediaControlsBaseJavaScript, sizeof(mediaControlsBaseJavaScript));
2060 scriptBuilder.append(mediaControlsGtkJavaScript, sizeof(mediaControlsGtkJavaScript));
2061 return scriptBuilder.toString();
2062}
2063#endif // ENABLE(VIDEO)
2064
2065#endif // GTK_API_VERSION_2
2066}
2067