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