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#ifndef GTK_API_VERSION_2
29
30#include "CSSValueKeywords.h"
31#include "GraphicsContext.h"
32#include "GtkVersioning.h"
33#include "HTMLNames.h"
34#include "MediaControlElements.h"
35#include "Page.h"
36#include "PaintInfo.h"
37#include "PlatformContextCairo.h"
38#include "RenderElement.h"
39#include "TextDirection.h"
40#include "UserAgentStyleSheets.h"
41#include <cmath>
42#include <gdk/gdk.h>
43#include <gtk/gtk.h>
44
45namespace WebCore {
46
47// This is the default value defined by GTK+, where it was defined as MIN_ARROW_SIZE in gtkarrow.c.
48static const int minArrowSize = 15;
49// This is the default value defined by GTK+, where it was defined as MIN_ARROW_WIDTH in gtkspinbutton.c.
50static const int minSpinButtonArrowSize = 6;
51
52typedef HashMap<GType, GRefPtr<GtkStyleContext> > StyleContextMap;
53static StyleContextMap& styleContextMap();
54
55static void gtkStyleChangedCallback(GObject*, GParamSpec*)
56{
57    StyleContextMap::const_iterator end = styleContextMap().end();
58    for (StyleContextMap::const_iterator iter = styleContextMap().begin(); iter != end; ++iter)
59        gtk_style_context_invalidate(iter->value.get());
60
61    Page::updateStyleForAllPagesAfterGlobalChangeInEnvironment();
62}
63
64static StyleContextMap& styleContextMap()
65{
66    DEPRECATED_DEFINE_STATIC_LOCAL(StyleContextMap, map, ());
67
68    static bool initialized = false;
69    if (!initialized) {
70        GtkSettings* settings = gtk_settings_get_default();
71        g_signal_connect(settings, "notify::gtk-theme-name", G_CALLBACK(gtkStyleChangedCallback), 0);
72        g_signal_connect(settings, "notify::gtk-color-scheme", G_CALLBACK(gtkStyleChangedCallback), 0);
73        initialized = true;
74    }
75    return map;
76}
77
78static GtkStyleContext* getStyleContext(GType widgetType)
79{
80    StyleContextMap::AddResult result = styleContextMap().add(widgetType, nullptr);
81    if (!result.isNewEntry)
82        return result.iterator->value.get();
83
84    GtkWidgetPath* path = gtk_widget_path_new();
85    gtk_widget_path_append_type(path, widgetType);
86
87    if (widgetType == GTK_TYPE_SCROLLBAR)
88        gtk_widget_path_iter_add_class(path, 0, GTK_STYLE_CLASS_SCROLLBAR);
89    else if (widgetType == GTK_TYPE_ENTRY)
90        gtk_widget_path_iter_add_class(path, 0, GTK_STYLE_CLASS_ENTRY);
91    else if (widgetType == GTK_TYPE_ARROW)
92        gtk_widget_path_iter_add_class(path, 0, "arrow");
93    else if (widgetType == GTK_TYPE_BUTTON) {
94        gtk_widget_path_iter_add_class(path, 0, GTK_STYLE_CLASS_BUTTON);
95        gtk_widget_path_iter_add_class(path, 1, "text-button");
96    }
97    else if (widgetType == GTK_TYPE_SCALE)
98        gtk_widget_path_iter_add_class(path, 0, GTK_STYLE_CLASS_SCALE);
99    else if (widgetType == GTK_TYPE_SEPARATOR)
100        gtk_widget_path_iter_add_class(path, 0, GTK_STYLE_CLASS_SEPARATOR);
101    else if (widgetType == GTK_TYPE_PROGRESS_BAR)
102        gtk_widget_path_iter_add_class(path, 0, GTK_STYLE_CLASS_PROGRESSBAR);
103    else if (widgetType == GTK_TYPE_SPIN_BUTTON)
104        gtk_widget_path_iter_add_class(path, 0, GTK_STYLE_CLASS_SPINBUTTON);
105    else if (widgetType == GTK_TYPE_TREE_VIEW)
106        gtk_widget_path_iter_add_class(path, 0, GTK_STYLE_CLASS_VIEW);
107    else if (widgetType == GTK_TYPE_CHECK_BUTTON)
108        gtk_widget_path_iter_add_class(path, 0, GTK_STYLE_CLASS_CHECK);
109    else if (widgetType == GTK_TYPE_RADIO_BUTTON)
110        gtk_widget_path_iter_add_class(path, 0, GTK_STYLE_CLASS_RADIO);
111
112    GRefPtr<GtkStyleContext> context = adoptGRef(gtk_style_context_new());
113    gtk_style_context_set_path(context.get(), path);
114    gtk_widget_path_free(path);
115
116    result.iterator->value = context;
117    return context.get();
118}
119
120GtkStyleContext* RenderThemeGtk::gtkScrollbarStyle()
121{
122    return getStyleContext(GTK_TYPE_SCROLLBAR);
123}
124
125// This is not a static method, because we want to avoid having GTK+ headers in RenderThemeGtk.h.
126extern GtkTextDirection gtkTextDirection(TextDirection);
127
128void RenderThemeGtk::platformInit()
129{
130}
131
132RenderThemeGtk::~RenderThemeGtk()
133{
134}
135
136#if ENABLE(VIDEO)
137void RenderThemeGtk::initMediaColors()
138{
139    GdkRGBA color;
140    GtkStyleContext* containerContext = getStyleContext(GTK_TYPE_CONTAINER);
141
142    gtk_style_context_get_background_color(containerContext, GTK_STATE_FLAG_NORMAL, &color);
143    m_panelColor = color;
144    gtk_style_context_get_background_color(containerContext, GTK_STATE_FLAG_ACTIVE, &color);
145    m_sliderColor = color;
146    gtk_style_context_get_background_color(containerContext, GTK_STATE_FLAG_SELECTED, &color);
147    m_sliderThumbColor = color;
148}
149#endif
150
151static void adjustRectForFocus(GtkStyleContext* context, FloatRect& rect)
152{
153    gint focusWidth, focusPad;
154    gtk_style_context_get_style(context,
155                                "focus-line-width", &focusWidth,
156                                "focus-padding", &focusPad, NULL);
157    rect.inflate(focusWidth + focusPad);
158}
159
160void RenderThemeGtk::adjustRepaintRect(const RenderObject& renderObject, FloatRect& rect)
161{
162    GtkStyleContext* context = 0;
163    bool checkInteriorFocus = false;
164    ControlPart part = renderObject.style().appearance();
165    switch (part) {
166    case CheckboxPart:
167    case RadioPart:
168        context = getStyleContext(part == CheckboxPart ? GTK_TYPE_CHECK_BUTTON : GTK_TYPE_RADIO_BUTTON);
169
170        gint indicatorSpacing;
171        gtk_style_context_get_style(context, "indicator-spacing", &indicatorSpacing, NULL);
172        rect.inflate(indicatorSpacing);
173
174        return;
175    case SliderVerticalPart:
176    case SliderHorizontalPart:
177        context = getStyleContext(GTK_TYPE_SCALE);
178        break;
179    case ButtonPart:
180    case MenulistButtonPart:
181    case MenulistPart:
182        context = getStyleContext(GTK_TYPE_BUTTON);
183        checkInteriorFocus = true;
184        break;
185    case TextFieldPart:
186    case TextAreaPart:
187        context = getStyleContext(GTK_TYPE_ENTRY);
188        checkInteriorFocus = true;
189        break;
190    default:
191        return;
192    }
193
194    ASSERT(context);
195    if (checkInteriorFocus) {
196        gboolean interiorFocus;
197        gtk_style_context_get_style(context, "interior-focus", &interiorFocus, NULL);
198        if (interiorFocus)
199            return;
200    }
201    adjustRectForFocus(context, rect);
202}
203
204static void setToggleSize(GtkStyleContext* context, RenderStyle& style)
205{
206    // The width and height are both specified, so we shouldn't change them.
207    if (!style.width().isIntrinsicOrAuto() && !style.height().isAuto())
208        return;
209
210    // Other ports hard-code this to 13 which is also the default value defined by GTK+.
211    // GTK+ users tend to demand the native look.
212    // It could be made a configuration option values other than 13 actually break site compatibility.
213    gint indicatorSize;
214    gtk_style_context_get_style(context, "indicator-size", &indicatorSize, NULL);
215
216    if (style.width().isIntrinsicOrAuto())
217        style.setWidth(Length(indicatorSize, Fixed));
218
219    if (style.height().isAuto())
220        style.setHeight(Length(indicatorSize, Fixed));
221}
222
223static void paintToggle(const RenderThemeGtk* theme, GType widgetType, const RenderObject& renderObject, const PaintInfo& paintInfo, const IntRect& fullRect)
224{
225    GtkStyleContext* context = getStyleContext(widgetType);
226    gtk_style_context_save(context);
227
228    // Some themes do not render large toggle buttons properly, so we simply
229    // shrink the rectangle back down to the default size and then center it
230    // in the full toggle button region. The reason for not simply forcing toggle
231    // buttons to be a smaller size is that we don't want to break site layouts.
232    gint indicatorSize;
233    gtk_style_context_get_style(context, "indicator-size", &indicatorSize, NULL);
234    IntRect rect(fullRect);
235    if (rect.width() > indicatorSize) {
236        rect.inflateX(-(rect.width() - indicatorSize) / 2);
237        rect.setWidth(indicatorSize); // In case rect.width() was equal to indicatorSize + 1.
238    }
239
240    if (rect.height() > indicatorSize) {
241        rect.inflateY(-(rect.height() - indicatorSize) / 2);
242        rect.setHeight(indicatorSize); // In case rect.height() was equal to indicatorSize + 1.
243    }
244
245    gtk_style_context_set_direction(context, static_cast<GtkTextDirection>(gtkTextDirection(renderObject.style().direction())));
246    gtk_style_context_add_class(context, widgetType == GTK_TYPE_CHECK_BUTTON ? GTK_STYLE_CLASS_CHECK : GTK_STYLE_CLASS_RADIO);
247
248    guint flags = 0;
249    if (!theme->isEnabled(renderObject) || theme->isReadOnlyControl(renderObject))
250        flags |= GTK_STATE_FLAG_INSENSITIVE;
251    else if (theme->isHovered(renderObject))
252        flags |= GTK_STATE_FLAG_PRELIGHT;
253    if (theme->isIndeterminate(renderObject))
254        flags |= GTK_STATE_FLAG_INCONSISTENT;
255    else if (theme->isChecked(renderObject))
256        flags |= GTK_STATE_FLAG_ACTIVE;
257    if (theme->isPressed(renderObject))
258        flags |= GTK_STATE_FLAG_SELECTED;
259    gtk_style_context_set_state(context, static_cast<GtkStateFlags>(flags));
260
261    if (widgetType == GTK_TYPE_CHECK_BUTTON)
262        gtk_render_check(context, paintInfo.context->platformContext()->cr(), rect.x(), rect.y(), rect.width(), rect.height());
263    else
264        gtk_render_option(context, paintInfo.context->platformContext()->cr(), rect.x(), rect.y(), rect.width(), rect.height());
265
266    if (theme->isFocused(renderObject)) {
267        IntRect indicatorRect(rect);
268        gint indicatorSpacing;
269        gtk_style_context_get_style(context, "indicator-spacing", &indicatorSpacing, NULL);
270        indicatorRect.inflate(indicatorSpacing);
271        gtk_render_focus(context, paintInfo.context->platformContext()->cr(), indicatorRect.x(), indicatorRect.y(),
272                         indicatorRect.width(), indicatorRect.height());
273    }
274
275    gtk_style_context_restore(context);
276}
277
278void RenderThemeGtk::setCheckboxSize(RenderStyle& style) const
279{
280    setToggleSize(getStyleContext(GTK_TYPE_CHECK_BUTTON), style);
281}
282
283bool RenderThemeGtk::paintCheckbox(const RenderObject& renderObject, const PaintInfo& paintInfo, const IntRect& rect)
284{
285    paintToggle(this, GTK_TYPE_CHECK_BUTTON, renderObject, paintInfo, rect);
286    return false;
287}
288
289void RenderThemeGtk::setRadioSize(RenderStyle& style) const
290{
291    setToggleSize(getStyleContext(GTK_TYPE_RADIO_BUTTON), style);
292}
293
294bool RenderThemeGtk::paintRadio(const RenderObject& renderObject, const PaintInfo& paintInfo, const IntRect& rect)
295{
296    paintToggle(this, GTK_TYPE_RADIO_BUTTON, renderObject, paintInfo, rect);
297    return false;
298}
299
300static void renderButton(RenderTheme* theme, GtkStyleContext* context, const RenderObject& renderObject, const PaintInfo& paintInfo, const IntRect& rect)
301{
302    IntRect buttonRect(rect);
303
304    guint flags = 0;
305    if (!theme->isEnabled(renderObject) || theme->isReadOnlyControl(renderObject))
306        flags |= GTK_STATE_FLAG_INSENSITIVE;
307    else if (theme->isHovered(renderObject))
308        flags |= GTK_STATE_FLAG_PRELIGHT;
309    if (theme->isPressed(renderObject))
310        flags |= GTK_STATE_FLAG_ACTIVE;
311    gtk_style_context_set_state(context, static_cast<GtkStateFlags>(flags));
312
313    if (theme->isDefault(renderObject)) {
314        GtkBorder* borderPtr = 0;
315        GtkBorder border = { 1, 1, 1, 1 };
316
317        gtk_style_context_get_style(context, "default-border", &borderPtr, NULL);
318        if (borderPtr) {
319            border = *borderPtr;
320            gtk_border_free(borderPtr);
321        }
322
323        buttonRect.move(border.left, border.top);
324        buttonRect.setWidth(buttonRect.width() - (border.left + border.right));
325        buttonRect.setHeight(buttonRect.height() - (border.top + border.bottom));
326
327        gtk_style_context_add_class(context, GTK_STYLE_CLASS_DEFAULT);
328    }
329
330    gtk_render_background(context, paintInfo.context->platformContext()->cr(), buttonRect.x(), buttonRect.y(), buttonRect.width(), buttonRect.height());
331    gtk_render_frame(context, paintInfo.context->platformContext()->cr(), buttonRect.x(), buttonRect.y(), buttonRect.width(), buttonRect.height());
332
333    if (theme->isFocused(renderObject)) {
334        gint focusWidth, focusPad;
335        gboolean displaceFocus, interiorFocus;
336        gtk_style_context_get_style(context,
337                                    "focus-line-width", &focusWidth,
338                                    "focus-padding", &focusPad,
339                                    "interior-focus", &interiorFocus,
340                                    "displace-focus", &displaceFocus,
341                                    NULL);
342
343        if (interiorFocus) {
344            GtkBorder borderWidth;
345            gtk_style_context_get_border(context, static_cast<GtkStateFlags>(flags), &borderWidth);
346
347            buttonRect = IntRect(buttonRect.x() + borderWidth.left + focusPad, buttonRect.y() + borderWidth.top + focusPad,
348                                 buttonRect.width() - (2 * focusPad + borderWidth.left + borderWidth.right),
349                                 buttonRect.height() - (2 * focusPad + borderWidth.top + borderWidth.bottom));
350        } else
351            buttonRect.inflate(focusWidth + focusPad);
352
353        if (displaceFocus && theme->isPressed(renderObject)) {
354            gint childDisplacementX;
355            gint childDisplacementY;
356            gtk_style_context_get_style(context,
357                                        "child-displacement-x", &childDisplacementX,
358                                        "child-displacement-y", &childDisplacementY,
359                                        NULL);
360            buttonRect.move(childDisplacementX, childDisplacementY);
361        }
362
363        gtk_render_focus(context, paintInfo.context->platformContext()->cr(), buttonRect.x(), buttonRect.y(), buttonRect.width(), buttonRect.height());
364    }
365}
366bool RenderThemeGtk::paintButton(const RenderObject& renderObject, const PaintInfo& paintInfo, const IntRect& rect)
367{
368    GtkStyleContext* context = getStyleContext(GTK_TYPE_BUTTON);
369    gtk_style_context_save(context);
370
371    gtk_style_context_set_direction(context, static_cast<GtkTextDirection>(gtkTextDirection(renderObject.style().direction())));
372    gtk_style_context_add_class(context, GTK_STYLE_CLASS_BUTTON);
373
374    renderButton(this, context, renderObject, paintInfo, rect);
375
376    gtk_style_context_restore(context);
377
378    return false;
379}
380
381static void getComboBoxMetrics(RenderStyle& style, GtkBorder& border, int& focus, int& separator)
382{
383    // If this menu list button isn't drawn using the native theme, we
384    // don't add any extra padding beyond what WebCore already uses.
385    if (style.appearance() == NoControlPart)
386        return;
387
388    GtkStyleContext* context = getStyleContext(GTK_TYPE_COMBO_BOX);
389    gtk_style_context_save(context);
390
391    gtk_style_context_add_class(context, GTK_STYLE_CLASS_BUTTON);
392    gtk_style_context_set_direction(context, static_cast<GtkTextDirection>(gtkTextDirection(style.direction())));
393
394    gtk_style_context_get_border(context, static_cast<GtkStateFlags>(0), &border);
395
396    gboolean interiorFocus;
397    gint focusWidth, focusPad;
398    gtk_style_context_get_style(context,
399                                "interior-focus", &interiorFocus,
400                                "focus-line-width", &focusWidth,
401                                "focus-padding", &focusPad, NULL);
402    focus = interiorFocus ? focusWidth + focusPad : 0;
403
404    gtk_style_context_restore(context);
405
406    context = getStyleContext(GTK_TYPE_SEPARATOR);
407    gtk_style_context_save(context);
408
409    GtkTextDirection direction = static_cast<GtkTextDirection>(gtkTextDirection(style.direction()));
410    gtk_style_context_set_direction(context, direction);
411    gtk_style_context_add_class(context, "separator");
412
413    gboolean wideSeparators;
414    gint separatorWidth;
415    gtk_style_context_get_style(context,
416                                "wide-separators", &wideSeparators,
417                                "separator-width", &separatorWidth,
418                                NULL);
419
420    // GTK+ always uses border.left, regardless of text direction. See gtkseperator.c.
421    if (!wideSeparators)
422        separatorWidth = border.left;
423
424    separator = separatorWidth;
425
426    gtk_style_context_restore(context);
427}
428
429int RenderThemeGtk::popupInternalPaddingLeft(RenderStyle& style) const
430{
431    GtkBorder borderWidth = { 0, 0, 0, 0 };
432    int focusWidth = 0, separatorWidth = 0;
433    getComboBoxMetrics(style, borderWidth, focusWidth, separatorWidth);
434    int left = borderWidth.left + focusWidth;
435    if (style.direction() == RTL)
436        left += separatorWidth + minArrowSize;
437    return left;
438}
439
440int RenderThemeGtk::popupInternalPaddingRight(RenderStyle& style) const
441{
442    GtkBorder borderWidth = { 0, 0, 0, 0 };
443    int focusWidth = 0, separatorWidth = 0;
444    getComboBoxMetrics(style, borderWidth, focusWidth, separatorWidth);
445    int right = borderWidth.right + focusWidth;
446    if (style.direction() == LTR)
447        right += separatorWidth + minArrowSize;
448    return right;
449}
450
451int RenderThemeGtk::popupInternalPaddingTop(RenderStyle& style) const
452{
453    GtkBorder borderWidth = { 0, 0, 0, 0 };
454    int focusWidth = 0, separatorWidth = 0;
455    getComboBoxMetrics(style, borderWidth, focusWidth, separatorWidth);
456    return borderWidth.top + focusWidth;
457}
458
459int RenderThemeGtk::popupInternalPaddingBottom(RenderStyle& style) const
460{
461    GtkBorder borderWidth = { 0, 0, 0, 0 };
462    int focusWidth = 0, separatorWidth = 0;
463    getComboBoxMetrics(style, borderWidth, focusWidth, separatorWidth);
464    return borderWidth.bottom + focusWidth;
465}
466
467bool RenderThemeGtk::paintMenuList(const RenderObject& renderObject, const PaintInfo& paintInfo, const FloatRect& r)
468{
469    // FIXME: adopt subpixel themes.
470    IntRect rect = IntRect(r);
471
472    cairo_t* cairoContext = paintInfo.context->platformContext()->cr();
473    GtkTextDirection direction = static_cast<GtkTextDirection>(gtkTextDirection(renderObject.style().direction()));
474
475    // Paint the button.
476    GtkStyleContext* buttonStyleContext = getStyleContext(GTK_TYPE_BUTTON);
477    gtk_style_context_save(buttonStyleContext);
478    gtk_style_context_set_direction(buttonStyleContext, direction);
479    gtk_style_context_add_class(buttonStyleContext, GTK_STYLE_CLASS_BUTTON);
480    renderButton(this, buttonStyleContext, renderObject, paintInfo, rect);
481
482    // Get the inner rectangle.
483    gint focusWidth, focusPad;
484    GtkBorder* innerBorderPtr = 0;
485    GtkBorder innerBorder = { 1, 1, 1, 1 };
486    gtk_style_context_get_style(buttonStyleContext,
487                                "inner-border", &innerBorderPtr,
488                                "focus-line-width", &focusWidth,
489                                "focus-padding", &focusPad,
490                                NULL);
491    if (innerBorderPtr) {
492        innerBorder = *innerBorderPtr;
493        gtk_border_free(innerBorderPtr);
494    }
495
496    GtkBorder borderWidth;
497    GtkStateFlags state = gtk_style_context_get_state(buttonStyleContext);
498    gtk_style_context_get_border(buttonStyleContext, state, &borderWidth);
499
500    focusWidth += focusPad;
501    IntRect innerRect(rect.x() + innerBorder.left + borderWidth.left + focusWidth,
502                      rect.y() + innerBorder.top + borderWidth.top + focusWidth,
503                      rect.width() - borderWidth.left - borderWidth.right - innerBorder.left - innerBorder.right - (2 * focusWidth),
504                      rect.height() - borderWidth.top - borderWidth.bottom - innerBorder.top - innerBorder.bottom - (2 * focusWidth));
505
506    if (isPressed(renderObject)) {
507        gint childDisplacementX;
508        gint childDisplacementY;
509        gtk_style_context_get_style(buttonStyleContext,
510                                    "child-displacement-x", &childDisplacementX,
511                                    "child-displacement-y", &childDisplacementY,
512                                    NULL);
513        innerRect.move(childDisplacementX, childDisplacementY);
514    }
515    innerRect.setWidth(std::max(1, innerRect.width()));
516    innerRect.setHeight(std::max(1, innerRect.height()));
517
518    gtk_style_context_restore(buttonStyleContext);
519
520    // Paint the arrow.
521    GtkStyleContext* arrowStyleContext = getStyleContext(GTK_TYPE_ARROW);
522    gtk_style_context_save(arrowStyleContext);
523
524    gtk_style_context_set_direction(arrowStyleContext, direction);
525    gtk_style_context_add_class(arrowStyleContext, "arrow");
526    gtk_style_context_add_class(arrowStyleContext, GTK_STYLE_CLASS_BUTTON);
527
528    gfloat arrowScaling;
529    gtk_style_context_get_style(arrowStyleContext, "arrow-scaling", &arrowScaling, NULL);
530
531    IntSize arrowSize(minArrowSize, innerRect.height());
532    FloatPoint arrowPosition(innerRect.location());
533    if (direction == GTK_TEXT_DIR_LTR)
534        arrowPosition.move(innerRect.width() - arrowSize.width(), 0);
535
536    // GTK+ actually fetches the xalign and valign values from the widget, but since we
537    // don't have a widget here, we are just using the default xalign and valign values of 0.5.
538    gint extent = std::min(arrowSize.width(), arrowSize.height()) * arrowScaling;
539    arrowPosition.move((arrowSize.width() - extent) / 2, (arrowSize.height() - extent) / 2);
540
541    gtk_style_context_set_state(arrowStyleContext, state);
542    gtk_render_arrow(arrowStyleContext, cairoContext, G_PI, arrowPosition.x(), arrowPosition.y(), extent);
543
544    gtk_style_context_restore(arrowStyleContext);
545
546    // Paint the separator if needed.
547    GtkStyleContext* separatorStyleContext = getStyleContext(GTK_TYPE_COMBO_BOX);
548    gtk_style_context_save(separatorStyleContext);
549
550    gtk_style_context_set_direction(separatorStyleContext, direction);
551    gtk_style_context_add_class(separatorStyleContext, "separator");
552
553    gboolean wideSeparators;
554    gint separatorWidth;
555    gtk_style_context_get_style(separatorStyleContext,
556                                "wide-separators", &wideSeparators,
557                                "separator-width", &separatorWidth,
558                                NULL);
559    if (wideSeparators && !separatorWidth) {
560        gtk_style_context_restore(separatorStyleContext);
561        return false;
562    }
563
564    gtk_style_context_set_state(separatorStyleContext, state);
565    IntPoint separatorPosition(arrowPosition.x(), innerRect.y());
566    if (wideSeparators) {
567        if (direction == GTK_TEXT_DIR_LTR)
568            separatorPosition.move(-separatorWidth, 0);
569        else
570            separatorPosition.move(arrowSize.width(), 0);
571
572        gtk_render_frame(separatorStyleContext, cairoContext,
573                         separatorPosition.x(), separatorPosition.y(),
574                         separatorWidth, innerRect.height());
575    } else {
576        GtkBorder padding;
577        gtk_style_context_get_padding(separatorStyleContext, state, &padding);
578        GtkBorder border;
579        gtk_style_context_get_border(separatorStyleContext, state, &border);
580
581        if (direction == GTK_TEXT_DIR_LTR)
582            separatorPosition.move(-(padding.left + border.left), 0);
583        else
584            separatorPosition.move(arrowSize.width(), 0);
585
586        cairo_save(cairoContext);
587
588        // An extra clip prevents the separator bleeding outside of the specified rectangle because of subpixel positioning.
589        cairo_rectangle(cairoContext, separatorPosition.x(), separatorPosition.y(), border.left, innerRect.height());
590        cairo_clip(cairoContext);
591        gtk_render_line(separatorStyleContext, cairoContext,
592                        separatorPosition.x(), separatorPosition.y(),
593                        separatorPosition.x(), innerRect.maxY());
594        cairo_restore(cairoContext);
595    }
596
597    gtk_style_context_restore(separatorStyleContext);
598    return false;
599}
600
601bool RenderThemeGtk::paintTextField(const RenderObject& renderObject, const PaintInfo& paintInfo, const FloatRect& rect)
602{
603    GtkStyleContext* context = getStyleContext(GTK_TYPE_ENTRY);
604    gtk_style_context_save(context);
605
606    gtk_style_context_set_direction(context, static_cast<GtkTextDirection>(gtkTextDirection(renderObject.style().direction())));
607    gtk_style_context_add_class(context, GTK_STYLE_CLASS_ENTRY);
608
609    guint flags = 0;
610    if (!isEnabled(renderObject) || isReadOnlyControl(renderObject))
611        flags |= GTK_STATE_FLAG_INSENSITIVE;
612    else if (isFocused(renderObject))
613        flags |= GTK_STATE_FLAG_FOCUSED;
614    gtk_style_context_set_state(context, static_cast<GtkStateFlags>(flags));
615
616    gtk_render_background(context, paintInfo.context->platformContext()->cr(), rect.x(), rect.y(), rect.width(), rect.height());
617    gtk_render_frame(context, paintInfo.context->platformContext()->cr(), rect.x(), rect.y(), rect.width(), rect.height());
618
619    if (isFocused(renderObject) && isEnabled(renderObject)) {
620        gboolean interiorFocus;
621        gint focusWidth, focusPad;
622        gtk_style_context_get_style(context,
623                                    "interior-focus", &interiorFocus,
624                                    "focus-line-width", &focusWidth,
625                                    "focus-padding", &focusPad,
626                                    NULL);
627        if (!interiorFocus) {
628            IntRect focusRect(rect);
629            focusRect.inflate(focusWidth + focusPad);
630            gtk_render_focus(context, paintInfo.context->platformContext()->cr(),
631                             focusRect.x(), focusRect.y(), focusRect.width(), focusRect.height());
632        }
633    }
634
635    gtk_style_context_restore(context);
636
637    return false;
638}
639
640static void applySliderStyleContextClasses(GtkStyleContext* context, ControlPart part)
641{
642    gtk_style_context_add_class(context, GTK_STYLE_CLASS_SCALE);
643    if (part == SliderHorizontalPart || part == SliderThumbHorizontalPart)
644        gtk_style_context_add_class(context, GTK_STYLE_CLASS_HORIZONTAL);
645    else if (part == SliderVerticalPart || part == SliderThumbVerticalPart)
646        gtk_style_context_add_class(context, GTK_STYLE_CLASS_VERTICAL);
647}
648
649bool RenderThemeGtk::paintSliderTrack(const RenderObject& renderObject, const PaintInfo& paintInfo, const IntRect& rect)
650{
651    ControlPart part = renderObject.style().appearance();
652    ASSERT_UNUSED(part, part == SliderHorizontalPart || part == SliderVerticalPart || part == MediaVolumeSliderPart);
653
654    GtkStyleContext* context = getStyleContext(GTK_TYPE_SCALE);
655    gtk_style_context_save(context);
656
657    gtk_style_context_set_direction(context, gtkTextDirection(renderObject.style().direction()));
658    applySliderStyleContextClasses(context, part);
659    gtk_style_context_add_class(context, GTK_STYLE_CLASS_TROUGH);
660
661    if (!isEnabled(renderObject) || isReadOnlyControl(renderObject))
662        gtk_style_context_set_state(context, GTK_STATE_FLAG_INSENSITIVE);
663
664    gtk_render_background(context, paintInfo.context->platformContext()->cr(),
665                          rect.x(), rect.y(), rect.width(), rect.height());
666    gtk_render_frame(context, paintInfo.context->platformContext()->cr(),
667                     rect.x(), rect.y(), rect.width(), rect.height());
668
669    if (isFocused(renderObject)) {
670        gint focusWidth, focusPad;
671        gtk_style_context_get_style(context,
672                                    "focus-line-width", &focusWidth,
673                                    "focus-padding", &focusPad, NULL);
674        IntRect focusRect(rect);
675        focusRect.inflate(focusWidth + focusPad);
676        gtk_render_focus(context, paintInfo.context->platformContext()->cr(),
677                         focusRect.x(), focusRect.y(), focusRect.width(), focusRect.height());
678    }
679
680    gtk_style_context_restore(context);
681    return false;
682}
683
684bool RenderThemeGtk::paintSliderThumb(const RenderObject& renderObject, const PaintInfo& paintInfo, const IntRect& rect)
685{
686    ControlPart part = renderObject.style().appearance();
687    ASSERT(part == SliderThumbHorizontalPart || part == SliderThumbVerticalPart || part == MediaVolumeSliderThumbPart);
688
689    GtkStyleContext* context = getStyleContext(GTK_TYPE_SCALE);
690    gtk_style_context_save(context);
691
692    gtk_style_context_set_direction(context, gtkTextDirection(renderObject.style().direction()));
693    applySliderStyleContextClasses(context, part);
694    gtk_style_context_add_class(context, GTK_STYLE_CLASS_SLIDER);
695
696    guint flags = 0;
697    if (!isEnabled(renderObject) || isReadOnlyControl(renderObject))
698        flags |= GTK_STATE_FLAG_INSENSITIVE;
699    else if (isHovered(renderObject))
700        flags |= GTK_STATE_FLAG_PRELIGHT;
701    if (isPressed(renderObject))
702        flags |= GTK_STATE_FLAG_ACTIVE;
703    gtk_style_context_set_state(context, static_cast<GtkStateFlags>(flags));
704
705    gtk_render_slider(context, paintInfo.context->platformContext()->cr(), rect.x(), rect.y(), rect.width(), rect.height(),
706                      part == SliderThumbHorizontalPart ? GTK_ORIENTATION_HORIZONTAL : GTK_ORIENTATION_VERTICAL);
707
708    gtk_style_context_restore(context);
709
710    return false;
711}
712
713void RenderThemeGtk::adjustSliderThumbSize(RenderStyle& style, Element&) const
714{
715    ControlPart part = style.appearance();
716    if (part != SliderThumbHorizontalPart && part != SliderThumbVerticalPart)
717        return;
718
719    gint sliderWidth, sliderLength;
720    gtk_style_context_get_style(getStyleContext(GTK_TYPE_SCALE),
721                                "slider-width", &sliderWidth,
722                                "slider-length", &sliderLength,
723                                NULL);
724    if (part == SliderThumbHorizontalPart) {
725        style.setWidth(Length(sliderLength, Fixed));
726        style.setHeight(Length(sliderWidth, Fixed));
727        return;
728    }
729    ASSERT(part == SliderThumbVerticalPart || part == MediaVolumeSliderThumbPart);
730    style.setWidth(Length(sliderWidth, Fixed));
731    style.setHeight(Length(sliderLength, Fixed));
732}
733
734bool RenderThemeGtk::paintProgressBar(const RenderObject& renderObject, const PaintInfo& paintInfo, const IntRect& rect)
735{
736    if (!renderObject.isProgress())
737        return true;
738
739    GtkStyleContext* context = getStyleContext(GTK_TYPE_PROGRESS_BAR);
740    gtk_style_context_save(context);
741
742    gtk_style_context_add_class(context, GTK_STYLE_CLASS_TROUGH);
743
744    gtk_render_background(context, paintInfo.context->platformContext()->cr(), rect.x(), rect.y(), rect.width(), rect.height());
745    gtk_render_frame(context, paintInfo.context->platformContext()->cr(), rect.x(), rect.y(), rect.width(), rect.height());
746
747    gtk_style_context_restore(context);
748
749    gtk_style_context_save(context);
750    gtk_style_context_add_class(context, GTK_STYLE_CLASS_PROGRESSBAR);
751
752
753    GtkBorder padding;
754    gtk_style_context_get_padding(context, static_cast<GtkStateFlags>(0), &padding);
755    IntRect progressRect(rect.x() + padding.left, rect.y() + padding.top,
756                         rect.width() - (padding.left + padding.right),
757                         rect.height() - (padding.top + padding.bottom));
758    progressRect = RenderThemeGtk::calculateProgressRect(renderObject, progressRect);
759
760    if (!progressRect.isEmpty())
761        gtk_render_activity(context, paintInfo.context->platformContext()->cr(), progressRect.x(), progressRect.y(), progressRect.width(), progressRect.height());
762
763    gtk_style_context_restore(context);
764    return false;
765}
766
767static gint spinButtonArrowSize(GtkStyleContext* context)
768{
769    PangoFontDescription* fontDescription;
770    gtk_style_context_get(context, static_cast<GtkStateFlags>(0), "font", &fontDescription, NULL);
771    gint fontSize = pango_font_description_get_size(fontDescription);
772    gint arrowSize = std::max(PANGO_PIXELS(fontSize), minSpinButtonArrowSize);
773    pango_font_description_free(fontDescription);
774
775    return arrowSize - arrowSize % 2; // Force even.
776}
777
778void RenderThemeGtk::adjustInnerSpinButtonStyle(StyleResolver&, RenderStyle& style, Element&) const
779{
780    GtkStyleContext* context = getStyleContext(GTK_TYPE_SPIN_BUTTON);
781
782    GtkBorder padding;
783    gtk_style_context_get_padding(context, static_cast<GtkStateFlags>(0), &padding);
784
785    int width = spinButtonArrowSize(context) + padding.left + padding.right;
786    style.setWidth(Length(width, Fixed));
787    style.setMinWidth(Length(width, Fixed));
788}
789
790static void paintSpinArrowButton(RenderTheme* theme, GtkStyleContext* context, const RenderObject& renderObject, const PaintInfo& paintInfo, const IntRect& rect, GtkArrowType arrowType)
791{
792    ASSERT(arrowType == GTK_ARROW_UP || arrowType == GTK_ARROW_DOWN);
793
794    gtk_style_context_save(context);
795    gtk_style_context_add_class(context, GTK_STYLE_CLASS_BUTTON);
796
797    GtkTextDirection direction = gtk_style_context_get_direction(context);
798    guint state = static_cast<guint>(gtk_style_context_get_state(context));
799    if (!(state & GTK_STATE_FLAG_INSENSITIVE)) {
800        if (theme->isPressed(renderObject)) {
801            if ((arrowType == GTK_ARROW_UP && theme->isSpinUpButtonPartPressed(renderObject))
802                || (arrowType == GTK_ARROW_DOWN && !theme->isSpinUpButtonPartPressed(renderObject)))
803                state |= GTK_STATE_FLAG_ACTIVE;
804        } else if (theme->isHovered(renderObject)) {
805            if ((arrowType == GTK_ARROW_UP && theme->isSpinUpButtonPartHovered(renderObject))
806                || (arrowType == GTK_ARROW_DOWN && !theme->isSpinUpButtonPartHovered(renderObject)))
807                state |= GTK_STATE_FLAG_PRELIGHT;
808        }
809    }
810    gtk_style_context_set_state(context, static_cast<GtkStateFlags>(state));
811
812    // Paint button.
813    IntRect buttonRect(rect);
814    guint junction = gtk_style_context_get_junction_sides(context);
815    if (arrowType == GTK_ARROW_UP)
816        junction |= GTK_JUNCTION_BOTTOM;
817    else {
818        junction |= GTK_JUNCTION_TOP;
819        buttonRect.move(0, rect.height() / 2);
820    }
821    buttonRect.setHeight(rect.height() / 2);
822    gtk_style_context_set_junction_sides(context, static_cast<GtkJunctionSides>(junction));
823
824    gtk_render_background(context, paintInfo.context->platformContext()->cr(), buttonRect.x(), buttonRect.y(), buttonRect.width(), buttonRect.height());
825    gtk_render_frame(context, paintInfo.context->platformContext()->cr(), buttonRect.x(), buttonRect.y(), buttonRect.width(), buttonRect.height());
826
827    // Paint arrow centered inside button.
828    // This code is based on gtkspinbutton.c code.
829    IntRect arrowRect;
830    gdouble angle;
831    if (arrowType == GTK_ARROW_UP) {
832        angle = 0;
833        arrowRect.setY(rect.y());
834        arrowRect.setHeight(rect.height() / 2 - 2);
835    } else {
836        angle = G_PI;
837        arrowRect.setY(rect.y() + buttonRect.y());
838        arrowRect.setHeight(rect.height() - arrowRect.y() - 2);
839    }
840    arrowRect.setWidth(rect.width() - 3);
841    if (direction == GTK_TEXT_DIR_LTR)
842        arrowRect.setX(rect.x() + 1);
843    else
844        arrowRect.setX(rect.x() + 2);
845
846    gint width = arrowRect.width() / 2;
847    width -= width % 2 - 1; // Force odd.
848    gint height = (width + 1) / 2;
849
850    arrowRect.move((arrowRect.width() - width) / 2, (arrowRect.height() - height) / 2);
851    gtk_render_arrow(context, paintInfo.context->platformContext()->cr(), angle, arrowRect.x(), arrowRect.y(), width);
852
853    gtk_style_context_restore(context);
854}
855
856bool RenderThemeGtk::paintInnerSpinButton(const RenderObject& renderObject, const PaintInfo& paintInfo, const IntRect& rect)
857{
858    GtkStyleContext* context = getStyleContext(GTK_TYPE_SPIN_BUTTON);
859    gtk_style_context_save(context);
860
861    GtkTextDirection direction = static_cast<GtkTextDirection>(gtkTextDirection(renderObject.style().direction()));
862    gtk_style_context_set_direction(context, direction);
863
864    guint flags = 0;
865    if (!isEnabled(renderObject) || isReadOnlyControl(renderObject))
866        flags |= GTK_STATE_FLAG_INSENSITIVE;
867    else if (isFocused(renderObject))
868        flags |= GTK_STATE_FLAG_FOCUSED;
869    gtk_style_context_set_state(context, static_cast<GtkStateFlags>(flags));
870    gtk_style_context_remove_class(context, GTK_STYLE_CLASS_ENTRY);
871
872    paintSpinArrowButton(this, context, renderObject, paintInfo, rect, GTK_ARROW_UP);
873    paintSpinArrowButton(this, context, renderObject, paintInfo, rect, GTK_ARROW_DOWN);
874
875    gtk_style_context_restore(context);
876
877    return false;
878}
879
880GRefPtr<GdkPixbuf> getStockIconForWidgetType(GType widgetType, const char* iconName, gint direction, gint state, gint iconSize)
881{
882    ASSERT(iconName);
883
884    GtkStyleContext* context = getStyleContext(widgetType);
885    GtkIconSet* iconSet = gtk_style_context_lookup_icon_set(context, iconName);
886
887    gtk_style_context_save(context);
888
889    guint flags = 0;
890    if (state == GTK_STATE_PRELIGHT)
891        flags |= GTK_STATE_FLAG_PRELIGHT;
892    else if (state == GTK_STATE_INSENSITIVE)
893        flags |= GTK_STATE_FLAG_INSENSITIVE;
894
895    gtk_style_context_set_state(context, static_cast<GtkStateFlags>(flags));
896    gtk_style_context_set_direction(context, static_cast<GtkTextDirection>(direction));
897    GdkPixbuf* icon = gtk_icon_set_render_icon_pixbuf(iconSet, context, static_cast<GtkIconSize>(iconSize));
898
899    gtk_style_context_restore(context);
900
901    return adoptGRef(icon);
902}
903
904GRefPtr<GdkPixbuf> getStockSymbolicIconForWidgetType(GType widgetType, const char* symbolicIconName, const char* fallbackStockIconName, gint direction, gint state, gint iconSize)
905{
906    GtkStyleContext* context = getStyleContext(widgetType);
907
908    gtk_style_context_save(context);
909
910    guint flags = 0;
911    if (state == GTK_STATE_PRELIGHT)
912        flags |= GTK_STATE_FLAG_PRELIGHT;
913    else if (state == GTK_STATE_INSENSITIVE)
914        flags |= GTK_STATE_FLAG_INSENSITIVE;
915
916    gtk_style_context_set_state(context, static_cast<GtkStateFlags>(flags));
917    gtk_style_context_set_direction(context, static_cast<GtkTextDirection>(direction));
918    GtkIconInfo* info = gtk_icon_theme_lookup_icon(gtk_icon_theme_get_default(), symbolicIconName, iconSize,
919        static_cast<GtkIconLookupFlags>(GTK_ICON_LOOKUP_FORCE_SVG | GTK_ICON_LOOKUP_FORCE_SIZE));
920    GdkPixbuf* icon = 0;
921    if (info) {
922        icon = gtk_icon_info_load_symbolic_for_context(info, context, 0, 0);
923        gtk_icon_info_free(info);
924    }
925
926    gtk_style_context_restore(context);
927
928    if (!icon) {
929        if (!fallbackStockIconName)
930            return nullptr;
931        return getStockIconForWidgetType(widgetType, fallbackStockIconName, direction, state, iconSize);
932    }
933
934    return adoptGRef(icon);
935}
936
937Color RenderThemeGtk::platformActiveSelectionBackgroundColor() const
938{
939    GdkRGBA gdkRGBAColor;
940    gtk_style_context_get_background_color(getStyleContext(GTK_TYPE_ENTRY), GTK_STATE_FLAG_SELECTED, &gdkRGBAColor);
941    return gdkRGBAColor;
942}
943
944Color RenderThemeGtk::platformInactiveSelectionBackgroundColor() const
945{
946    GdkRGBA gdkRGBAColor;
947    gtk_style_context_get_background_color(getStyleContext(GTK_TYPE_ENTRY), GTK_STATE_FLAG_ACTIVE, &gdkRGBAColor);
948    return gdkRGBAColor;
949}
950
951Color RenderThemeGtk::platformActiveSelectionForegroundColor() const
952{
953    GdkRGBA gdkRGBAColor;
954    gtk_style_context_get_color(getStyleContext(GTK_TYPE_ENTRY), GTK_STATE_FLAG_SELECTED, &gdkRGBAColor);
955    return gdkRGBAColor;
956}
957
958Color RenderThemeGtk::platformInactiveSelectionForegroundColor() const
959{
960    GdkRGBA gdkRGBAColor;
961    gtk_style_context_get_color(getStyleContext(GTK_TYPE_ENTRY), GTK_STATE_FLAG_ACTIVE, &gdkRGBAColor);
962    return gdkRGBAColor;
963}
964
965Color RenderThemeGtk::platformActiveListBoxSelectionBackgroundColor() const
966{
967    GdkRGBA gdkRGBAColor;
968    gtk_style_context_get_background_color(getStyleContext(GTK_TYPE_TREE_VIEW), GTK_STATE_FLAG_SELECTED, &gdkRGBAColor);
969    return gdkRGBAColor;
970}
971
972Color RenderThemeGtk::platformInactiveListBoxSelectionBackgroundColor() const
973{
974    GdkRGBA gdkRGBAColor;
975    gtk_style_context_get_background_color(getStyleContext(GTK_TYPE_TREE_VIEW), GTK_STATE_FLAG_ACTIVE, &gdkRGBAColor);
976    return gdkRGBAColor;
977}
978
979Color RenderThemeGtk::platformActiveListBoxSelectionForegroundColor() const
980{
981    GdkRGBA gdkRGBAColor;
982    gtk_style_context_get_color(getStyleContext(GTK_TYPE_TREE_VIEW), GTK_STATE_FLAG_SELECTED, &gdkRGBAColor);
983    return gdkRGBAColor;
984}
985
986Color RenderThemeGtk::platformInactiveListBoxSelectionForegroundColor() const
987{
988    GdkRGBA gdkRGBAColor;
989    gtk_style_context_get_color(getStyleContext(GTK_TYPE_TREE_VIEW), GTK_STATE_FLAG_ACTIVE, &gdkRGBAColor);
990    return gdkRGBAColor;
991}
992
993Color RenderThemeGtk::systemColor(CSSValueID cssValueId) const
994{
995    GdkRGBA gdkRGBAColor;
996
997    switch (cssValueId) {
998    case CSSValueButtontext:
999        gtk_style_context_get_color(getStyleContext(GTK_TYPE_BUTTON), GTK_STATE_FLAG_ACTIVE, &gdkRGBAColor);
1000        return gdkRGBAColor;
1001    case CSSValueCaptiontext:
1002        gtk_style_context_get_color(getStyleContext(GTK_TYPE_ENTRY), static_cast<GtkStateFlags>(0), &gdkRGBAColor);
1003        return gdkRGBAColor;
1004    default:
1005        return RenderTheme::systemColor(cssValueId);
1006    }
1007}
1008
1009} // namespace WebCore
1010
1011#endif // !GTK_API_VERSION_2
1012