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#ifdef GTK_API_VERSION_2
29
30// We need this to allow building while using GTK_WIDGET_SET_FLAGS. It's deprecated
31// but some theme engines require it to ensure proper rendering of focus indicators.
32#undef GTK_DISABLE_DEPRECATED
33
34#include "CSSValueKeywords.h"
35#include "Font.h"
36#include "GraphicsContext.h"
37#include "GtkVersioning.h"
38#include "HTMLNames.h"
39#include "MediaControlElements.h"
40#include "PaintInfo.h"
41#include "RenderObject.h"
42#include "TextDirection.h"
43#include "UserAgentStyleSheets.h"
44#include "WidgetRenderingContext.h"
45#include <gdk/gdk.h>
46#include <gtk/gtk.h>
47
48namespace WebCore {
49
50// This is the default value defined by GTK+, where it was defined as MIN_ARROW_WIDTH in gtkspinbutton.c.
51static const int minSpinButtonArrowSize = 6;
52
53// This is not a static method, because we want to avoid having GTK+ headers in RenderThemeGtk.h.
54extern GtkTextDirection gtkTextDirection(TextDirection);
55
56void RenderThemeGtk::platformInit()
57{
58    m_themePartsHaveRGBAColormap = true;
59    m_gtkWindow = 0;
60    m_gtkContainer = 0;
61    m_gtkButton = 0;
62    m_gtkEntry = 0;
63    m_gtkTreeView = 0;
64    m_gtkVScale = 0;
65    m_gtkHScale = 0;
66    m_gtkRadioButton = 0;
67    m_gtkCheckButton = 0;
68    m_gtkProgressBar = 0;
69    m_gtkComboBox = 0;
70    m_gtkComboBoxButton = 0;
71    m_gtkComboBoxArrow = 0;
72    m_gtkComboBoxSeparator = 0;
73    m_gtkVScrollbar = 0;
74    m_gtkHScrollbar = 0;
75    m_gtkSpinButton = 0;
76
77    m_colormap = gdk_screen_get_rgba_colormap(gdk_screen_get_default());
78    if (!m_colormap) {
79        m_themePartsHaveRGBAColormap = false;
80        m_colormap = gdk_screen_get_default_colormap(gdk_screen_get_default());
81    }
82}
83
84RenderThemeGtk::~RenderThemeGtk()
85{
86    if (m_gtkWindow)
87        gtk_widget_destroy(m_gtkWindow);
88}
89
90#if ENABLE(VIDEO)
91void RenderThemeGtk::initMediaColors()
92{
93    GtkStyle* style = gtk_widget_get_style(GTK_WIDGET(gtkContainer()));
94    m_panelColor = style->bg[GTK_STATE_NORMAL];
95    m_sliderColor = style->bg[GTK_STATE_ACTIVE];
96    m_sliderThumbColor = style->bg[GTK_STATE_SELECTED];
97}
98#endif
99
100static void adjustRectForFocus(GtkWidget* widget, IntRect& rect, bool ignoreInteriorFocusProperty = false)
101{
102    gint focusWidth, focusPad;
103    gboolean interiorFocus = 0;
104    gtk_widget_style_get(widget,
105                         "interior-focus", &interiorFocus,
106                         "focus-line-width", &focusWidth,
107                         "focus-padding", &focusPad, NULL);
108    if (!ignoreInteriorFocusProperty && interiorFocus)
109        return;
110    rect.inflate(focusWidth + focusPad);
111}
112
113void RenderThemeGtk::adjustRepaintRect(const RenderObject* renderObject, IntRect& rect)
114{
115    ControlPart part = renderObject->style()->appearance();
116    switch (part) {
117    case CheckboxPart:
118    case RadioPart: {
119        // We ignore the interior focus property and always expand the focus rect. In GTK+, the
120        // focus indicator is usually on the text next to a checkbox or radio button, but that doesn't
121        // happen in WebCore. By expanding the focus rectangle unconditionally we increase its prominence.
122        adjustRectForFocus(part == CheckboxPart ? gtkCheckButton() : gtkRadioButton(), rect, true);
123        return;
124    }
125    case InnerSpinButtonPart:
126        // See paintInnerSpinButton for an explanation of why we expand the painting rect.
127        rect.inflateY(2);
128        rect.setWidth(rect.width() + 2);
129    default:
130        return;
131    }
132}
133
134static GtkStateType getGtkStateType(RenderThemeGtk* theme, RenderObject* object)
135{
136    if (!theme->isEnabled(object) || theme->isReadOnlyControl(object))
137        return GTK_STATE_INSENSITIVE;
138    if (theme->isPressed(object))
139        return GTK_STATE_ACTIVE;
140    if (theme->isHovered(object))
141        return GTK_STATE_PRELIGHT;
142    return GTK_STATE_NORMAL;
143}
144
145static void setToggleSize(const RenderThemeGtk* theme, RenderStyle* style, GtkWidget* widget)
146{
147    // The width and height are both specified, so we shouldn't change them.
148    if (!style->width().isIntrinsicOrAuto() && !style->height().isAuto())
149        return;
150
151    gint indicatorSize;
152    gtk_widget_style_get(widget, "indicator-size", &indicatorSize, NULL);
153    if (style->width().isIntrinsicOrAuto())
154        style->setWidth(Length(indicatorSize, Fixed));
155    if (style->height().isAuto())
156        style->setHeight(Length(indicatorSize, Fixed));
157}
158
159static void paintToggle(RenderThemeGtk* theme, RenderObject* renderObject, const PaintInfo& info, const IntRect& rect, GtkWidget* widget)
160{
161    // We do not call gtk_toggle_button_set_active here, because some themes begin a series of
162    // animation frames in a "toggled" signal handler. This puts some checkboxes in a half-way
163    // checked state. Every GTK+ theme I tested merely looks at the shadow type (and not the
164    // 'active' property) to determine whether or not to draw the check.
165    gtk_widget_set_sensitive(widget, theme->isEnabled(renderObject) && !theme->isReadOnlyControl(renderObject));
166    gtk_widget_set_direction(widget, gtkTextDirection(renderObject->style()->direction()));
167
168    bool indeterminate = theme->isIndeterminate(renderObject);
169    gtk_toggle_button_set_inconsistent(GTK_TOGGLE_BUTTON(widget), indeterminate);
170
171    GtkShadowType shadowType = GTK_SHADOW_OUT;
172    if (indeterminate) // This originates from the Mozilla code.
173        shadowType = GTK_SHADOW_ETCHED_IN;
174    else if (theme->isChecked(renderObject))
175        shadowType = GTK_SHADOW_IN;
176
177    WidgetRenderingContext widgetContext(info.context, rect);
178    IntRect buttonRect(IntPoint(), rect.size());
179    GtkStateType toggleState = getGtkStateType(theme, renderObject);
180    const char* detail = 0;
181    if (GTK_IS_RADIO_BUTTON(widget)) {
182        detail = "radiobutton";
183        widgetContext.gtkPaintOption(buttonRect, widget, toggleState, shadowType, detail);
184    } else {
185        detail = "checkbutton";
186        widgetContext.gtkPaintCheck(buttonRect, widget, toggleState, shadowType, detail);
187    }
188
189    if (theme->isFocused(renderObject)) {
190        IntRect focusRect(buttonRect);
191        adjustRectForFocus(widget, focusRect, true);
192        widgetContext.gtkPaintFocus(focusRect, widget, toggleState, detail);
193    }
194}
195
196void RenderThemeGtk::setCheckboxSize(RenderStyle* style) const
197{
198    setToggleSize(this, style, gtkCheckButton());
199}
200
201bool RenderThemeGtk::paintCheckbox(RenderObject* renderObject, const PaintInfo& info, const IntRect& rect)
202{
203    paintToggle(this, renderObject, info, rect, gtkCheckButton());
204    return false;
205}
206
207void RenderThemeGtk::setRadioSize(RenderStyle* style) const
208{
209    setToggleSize(this, style, gtkRadioButton());
210}
211
212bool RenderThemeGtk::paintRadio(RenderObject* renderObject, const PaintInfo& info, const IntRect& rect)
213{
214    paintToggle(this, renderObject, info, rect, gtkRadioButton());
215    return false;
216}
217
218static void setWidgetHasFocus(GtkWidget* widget, gboolean hasFocus)
219{
220    g_object_set(widget, "has-focus", hasFocus, NULL);
221
222    // These functions are deprecated in GTK+ 2.22, yet theme engines still look
223    // at these flags when determining if a widget has focus, so we must use them.
224    if (hasFocus)
225        GTK_WIDGET_SET_FLAGS(widget, GTK_HAS_FOCUS);
226    else
227        GTK_WIDGET_UNSET_FLAGS(widget, GTK_HAS_FOCUS);
228}
229
230bool RenderThemeGtk::paintButton(RenderObject* object, const PaintInfo& info, const IntRect& rect)
231{
232    if (info.context->paintingDisabled())
233        return false;
234
235    GtkWidget* widget = gtkButton();
236    IntRect buttonRect(IntPoint(), rect.size());
237    IntRect focusRect(buttonRect);
238
239    GtkStateType state = getGtkStateType(this, object);
240    gtk_widget_set_state(widget, state);
241    gtk_widget_set_direction(widget, gtkTextDirection(object->style()->direction()));
242
243    if (isFocused(object)) {
244        setWidgetHasFocus(widget, TRUE);
245
246        gboolean interiorFocus = 0, focusWidth = 0, focusPadding = 0;
247        gtk_widget_style_get(widget,
248                             "interior-focus", &interiorFocus,
249                             "focus-line-width", &focusWidth,
250                             "focus-padding", &focusPadding, NULL);
251        // If we are using exterior focus, we shrink the button rect down before
252        // drawing. If we are using interior focus we shrink the focus rect. This
253        // approach originates from the Mozilla theme drawing code (gtk2drawing.c).
254        if (interiorFocus) {
255            GtkStyle* style = gtk_widget_get_style(widget);
256            focusRect.inflateX(-style->xthickness - focusPadding);
257            focusRect.inflateY(-style->ythickness - focusPadding);
258        } else {
259            buttonRect.inflateX(-focusWidth - focusPadding);
260            buttonRect.inflateY(-focusPadding - focusPadding);
261        }
262    }
263
264    WidgetRenderingContext widgetContext(info.context, rect);
265    GtkShadowType shadowType = state == GTK_STATE_ACTIVE ? GTK_SHADOW_IN : GTK_SHADOW_OUT;
266    widgetContext.gtkPaintBox(buttonRect, widget, state, shadowType, "button");
267    if (isFocused(object))
268        widgetContext.gtkPaintFocus(focusRect, widget, state, "button");
269
270    setWidgetHasFocus(widget, FALSE);
271    return false;
272}
273
274int RenderThemeGtk::getComboBoxSeparatorWidth() const
275{
276    GtkWidget* separator = gtkComboBoxSeparator();
277    if (!separator)
278        return 0;
279
280    gboolean hasWideSeparators = FALSE;
281    gint separatorWidth = 0;
282    gtk_widget_style_get(separator,
283                         "wide-separators", &hasWideSeparators,
284                         "separator-width", &separatorWidth,
285                         NULL);
286    if (hasWideSeparators)
287        return separatorWidth;
288    return gtk_widget_get_style(separator)->xthickness;
289}
290
291int RenderThemeGtk::comboBoxArrowSize(RenderStyle* style) const
292{
293    // Taking the font size and reversing the DPI conversion seems to match
294    // GTK+ rendering as closely as possible.
295    return style->font().size() * (72.0 / RenderThemeGtk::getScreenDPI());
296}
297
298static void getButtonInnerBorder(GtkWidget* button, int& left, int& top, int& right, int& bottom)
299{
300    GtkStyle* style = gtk_widget_get_style(button);
301    int outerBorder = gtk_container_get_border_width(GTK_CONTAINER(button));
302    static GtkBorder defaultInnerBorder = {1, 1, 1, 1};
303    GtkBorder* innerBorder;
304    gtk_widget_style_get(button, "inner-border", &innerBorder, NULL);
305    if (!innerBorder)
306        innerBorder = &defaultInnerBorder;
307
308    left = outerBorder + innerBorder->left + style->xthickness;
309    right = outerBorder + innerBorder->right + style->xthickness;
310    top = outerBorder + innerBorder->top + style->ythickness;
311    bottom = outerBorder + innerBorder->bottom + style->ythickness;
312
313    if (innerBorder != &defaultInnerBorder)
314        gtk_border_free(innerBorder);
315}
316
317
318void RenderThemeGtk::getComboBoxPadding(RenderStyle* style, int& left, int& top, int& right, int& bottom) const
319{
320    // If this menu list button isn't drawn using the native theme, we
321    // don't add any extra padding beyond what WebCore already uses.
322    if (style->appearance() == NoControlPart)
323        return;
324
325    // A combo box button is a button with widgets packed into it.
326    GtkStyle* buttonWidgetStyle = gtk_widget_get_style(gtkComboBoxButton());
327    getButtonInnerBorder(gtkComboBoxButton(), left, top, right, bottom);
328
329    // Add xthickness amount of padding for each side of the separator. This ensures
330    // that the text does not bump up against the separator.
331    int arrowAndSeperatorLength = comboBoxArrowSize(style) +
332        getComboBoxSeparatorWidth() + (3 * buttonWidgetStyle->xthickness);
333
334    if (style->direction() == RTL)
335        left += arrowAndSeperatorLength;
336    else
337        right += arrowAndSeperatorLength;
338}
339
340int RenderThemeGtk::popupInternalPaddingLeft(RenderStyle* style) const
341{
342    int left = 0, top = 0, right = 0, bottom = 0;
343    getComboBoxPadding(style, left, top, right, bottom);
344    return left;
345}
346
347int RenderThemeGtk::popupInternalPaddingRight(RenderStyle* style) const
348{
349    int left = 0, top = 0, right = 0, bottom = 0;
350    getComboBoxPadding(style, left, top, right, bottom);
351    return right;
352}
353
354int RenderThemeGtk::popupInternalPaddingTop(RenderStyle* style) const
355{
356    int left = 0, top = 0, right = 0, bottom = 0;
357    getComboBoxPadding(style, left, top, right, bottom);
358    return top;
359}
360
361int RenderThemeGtk::popupInternalPaddingBottom(RenderStyle* style) const
362{
363    int left = 0, top = 0, right = 0, bottom = 0;
364    getComboBoxPadding(style, left, top, right, bottom);
365    return bottom;
366}
367
368bool RenderThemeGtk::paintMenuList(RenderObject* object, const PaintInfo& info, const IntRect& rect)
369{
370    if (paintButton(object, info, rect))
371        return true;
372
373    // Menu list button painting strategy.
374    // For buttons with appears-as-list set to false (having a separator):
375    // | left border | Button text | xthickness | vseparator | xthickness | arrow | xthickness | right border |
376    // For buttons with appears-as-list set to true (not having a separator):
377    // | left border | Button text | arrow | xthickness | right border |
378
379    int leftBorder = 0, rightBorder = 0, bottomBorder = 0, topBorder = 0;
380    getButtonInnerBorder(gtkComboBoxButton(), leftBorder, topBorder, rightBorder, bottomBorder);
381    RenderStyle* style = object->style();
382    int arrowSize = comboBoxArrowSize(style);
383    GtkStyle* buttonStyle = gtk_widget_get_style(gtkComboBoxButton());
384
385    IntRect arrowRect(0, (rect.height() - arrowSize) / 2, arrowSize, arrowSize);
386    if (style->direction() == RTL)
387        arrowRect.setX(leftBorder + buttonStyle->xthickness);
388    else
389        arrowRect.setX(rect.width() - rightBorder - buttonStyle->xthickness - arrowSize);
390    GtkShadowType shadowType = isPressed(object) ? GTK_SHADOW_IN : GTK_SHADOW_OUT;
391
392    WidgetRenderingContext widgetContext(info.context, rect);
393    GtkStateType stateType = getGtkStateType(this, object);
394    widgetContext.gtkPaintArrow(arrowRect, gtkComboBoxArrow(), stateType, shadowType, GTK_ARROW_DOWN, "arrow");
395
396    // Some combo boxes do not have a separator.
397    GtkWidget* separator = gtkComboBoxSeparator();
398    if (!separator)
399        return false;
400
401    // We want to decrease the height of the separator based on the focus padding of the button.
402    gint focusPadding = 0, focusWidth = 0;
403    gtk_widget_style_get(gtkComboBoxButton(),
404                         "focus-line-width", &focusWidth,
405                         "focus-padding", &focusPadding, NULL);
406    topBorder += focusPadding + focusWidth;
407    bottomBorder += focusPadding + focusWidth;
408    int separatorWidth = getComboBoxSeparatorWidth();
409    IntRect separatorRect(0, topBorder, separatorWidth, rect.height() - topBorder - bottomBorder);
410    if (style->direction() == RTL)
411        separatorRect.setX(arrowRect.x() + arrowRect.width() + buttonStyle->xthickness + separatorWidth);
412    else
413        separatorRect.setX(arrowRect.x() - buttonStyle->xthickness - separatorWidth);
414
415    gboolean hasWideSeparators = FALSE;
416    gtk_widget_style_get(separator, "wide-separators", &hasWideSeparators, NULL);
417    if (hasWideSeparators)
418        widgetContext.gtkPaintBox(separatorRect, separator, GTK_STATE_NORMAL, GTK_SHADOW_ETCHED_OUT, "vseparator");
419    else
420        widgetContext.gtkPaintVLine(separatorRect, separator, GTK_STATE_NORMAL, "vseparator");
421
422    return false;
423}
424
425bool RenderThemeGtk::paintTextField(RenderObject* renderObject, const PaintInfo& info, const IntRect& rect)
426{
427    GtkWidget* widget = gtkEntry();
428
429    bool enabled = isEnabled(renderObject) && !isReadOnlyControl(renderObject);
430    GtkStateType backgroundState = enabled ? GTK_STATE_NORMAL : GTK_STATE_INSENSITIVE;
431    gtk_widget_set_sensitive(widget, enabled);
432    gtk_widget_set_direction(widget, gtkTextDirection(renderObject->style()->direction()));
433    setWidgetHasFocus(widget, isFocused(renderObject));
434
435    WidgetRenderingContext widgetContext(info.context, rect);
436    IntRect textFieldRect(IntPoint(), rect.size());
437
438    // The entry background is only painted over the interior part of the GTK+ entry, not
439    // the entire frame. This happens in the Mozilla theme drawing code as well.
440    IntRect interiorRect(textFieldRect);
441    GtkStyle* style = gtk_widget_get_style(widget);
442    interiorRect.inflateX(-style->xthickness);
443    interiorRect.inflateY(-style->ythickness);
444    widgetContext.gtkPaintFlatBox(interiorRect, widget, backgroundState, GTK_SHADOW_NONE, "entry_bg");
445
446    // This is responsible for drawing the actual frame.
447    widgetContext.gtkPaintShadow(textFieldRect, widget, GTK_STATE_NORMAL, GTK_SHADOW_IN, "entry");
448
449    gboolean interiorFocus;
450    gint focusWidth;
451    gtk_widget_style_get(widget,
452                         "interior-focus", &interiorFocus,
453                         "focus-line-width", &focusWidth,  NULL);
454    if (isFocused(renderObject) && !interiorFocus) {
455        // When GTK+ paints a text entry with focus, it shrinks the size of the frame area by the
456        // focus width and paints over the previously unfocused text entry. We need to emulate that
457        // by drawing both the unfocused frame above and the focused frame here.
458        IntRect shadowRect(textFieldRect);
459        shadowRect.inflate(-focusWidth);
460        widgetContext.gtkPaintShadow(shadowRect, widget, GTK_STATE_NORMAL, GTK_SHADOW_IN, "entry");
461
462        widgetContext.gtkPaintFocus(textFieldRect, widget, GTK_STATE_NORMAL, "entry");
463    }
464
465    return false;
466}
467
468bool RenderThemeGtk::paintSliderTrack(RenderObject* object, const PaintInfo& info, const IntRect& rect)
469{
470    if (info.context->paintingDisabled())
471        return false;
472
473    ControlPart part = object->style()->appearance();
474    ASSERT(part == SliderHorizontalPart || part == SliderVerticalPart || part == MediaVolumeSliderPart);
475
476    // We shrink the trough rect slightly to make room for the focus indicator.
477    IntRect troughRect(IntPoint(), rect.size()); // This is relative to rect.
478    GtkWidget* widget = 0;
479    if (part == SliderHorizontalPart) {
480        widget = gtkHScale();
481        troughRect.inflateX(-gtk_widget_get_style(widget)->xthickness);
482    } else {
483        widget = gtkVScale();
484        troughRect.inflateY(-gtk_widget_get_style(widget)->ythickness);
485    }
486    gtk_widget_set_direction(widget, gtkTextDirection(object->style()->direction()));
487
488    WidgetRenderingContext widgetContext(info.context, rect);
489    widgetContext.gtkPaintBox(troughRect, widget, GTK_STATE_ACTIVE, GTK_SHADOW_OUT, "trough");
490    if (isFocused(object))
491        widgetContext.gtkPaintFocus(IntRect(IntPoint(), rect.size()), widget, getGtkStateType(this, object), "trough");
492
493    return false;
494}
495
496bool RenderThemeGtk::paintSliderThumb(RenderObject* object, const PaintInfo& info, const IntRect& rect)
497{
498    if (info.context->paintingDisabled())
499        return false;
500
501    ControlPart part = object->style()->appearance();
502    ASSERT(part == SliderThumbHorizontalPart || part == SliderThumbVerticalPart || part == MediaVolumeSliderThumbPart);
503
504    GtkWidget* widget = 0;
505    const char* detail = 0;
506    GtkOrientation orientation;
507    if (part == SliderThumbHorizontalPart) {
508        widget = gtkHScale();
509        detail = "hscale";
510        orientation = GTK_ORIENTATION_HORIZONTAL;
511    } else {
512        widget = gtkVScale();
513        detail = "vscale";
514        orientation = GTK_ORIENTATION_VERTICAL;
515    }
516    gtk_widget_set_direction(widget, gtkTextDirection(object->style()->direction()));
517
518    // Only some themes have slider thumbs respond to clicks and some don't. This information is
519    // gathered via the 'activate-slider' property, but it's deprecated in GTK+ 2.22 and removed in
520    // GTK+ 3.x. The drawback of not honoring it is that slider thumbs change color when you click
521    // on them.
522    IntRect thumbRect(IntPoint(), rect.size());
523    WidgetRenderingContext widgetContext(info.context, rect);
524    widgetContext.gtkPaintSlider(thumbRect, widget, getGtkStateType(this, object), GTK_SHADOW_OUT, detail, orientation);
525    return false;
526}
527
528void RenderThemeGtk::adjustSliderThumbSize(RenderStyle* style, Element*) const
529{
530    ControlPart part = style->appearance();
531    if (part != SliderThumbHorizontalPart && part != SliderThumbVerticalPart)
532        return;
533
534    GtkWidget* widget = part == SliderThumbHorizontalPart ? gtkHScale() : gtkVScale();
535    int length = 0, width = 0;
536    gtk_widget_style_get(widget,
537                         "slider_length", &length,
538                         "slider_width", &width,
539                         NULL);
540
541    if (part == SliderThumbHorizontalPart) {
542        style->setWidth(Length(length, Fixed));
543        style->setHeight(Length(width, Fixed));
544        return;
545    }
546    ASSERT(part == SliderThumbVerticalPart || part == MediaVolumeSliderThumbPart);
547    style->setWidth(Length(width, Fixed));
548    style->setHeight(Length(length, Fixed));
549}
550
551#if ENABLE(PROGRESS_ELEMENT)
552bool RenderThemeGtk::paintProgressBar(RenderObject* renderObject, const PaintInfo& paintInfo, const IntRect& rect)
553{
554    GtkWidget* widget = gtkProgressBar();
555    gtk_widget_set_direction(widget, gtkTextDirection(renderObject->style()->direction()));
556
557    WidgetRenderingContext widgetContext(paintInfo.context, rect);
558    IntRect fullProgressBarRect(IntPoint(), rect.size());
559    widgetContext.gtkPaintBox(fullProgressBarRect, widget, GTK_STATE_NORMAL, GTK_SHADOW_IN, "trough");
560
561    GtkStyle* style = gtk_widget_get_style(widget);
562    IntRect progressRect(fullProgressBarRect);
563    progressRect.inflateX(-style->xthickness);
564    progressRect.inflateY(-style->ythickness);
565    progressRect = RenderThemeGtk::calculateProgressRect(renderObject, progressRect);
566
567    if (!progressRect.isEmpty())
568        widgetContext.gtkPaintBox(progressRect, widget, GTK_STATE_PRELIGHT, GTK_SHADOW_OUT, "bar");
569
570    return false;
571}
572#endif
573
574void RenderThemeGtk::adjustInnerSpinButtonStyle(StyleResolver*, RenderStyle* style, Element*) const
575{
576    GtkStyle* gtkStyle = gtk_widget_get_style(gtkSpinButton());
577    const PangoFontDescription* fontDescription = gtkStyle->font_desc;
578    gint fontSize = pango_font_description_get_size(fontDescription);
579
580    // Force an odd arrow size here. GTK+ 3.x forces even in this case, but
581    // Nodoka-based themes look incorrect with an even arrow size.
582    int width = max(PANGO_PIXELS(fontSize), minSpinButtonArrowSize);
583    width += -((width % 2) - 1) + gtkStyle->xthickness;
584
585    style->setWidth(Length(width, Fixed));
586    style->setMinWidth(Length(width, Fixed));
587}
588
589bool RenderThemeGtk::paintInnerSpinButton(RenderObject* renderObject, const PaintInfo& paintInfo, const IntRect& rect)
590{
591    // We expand the painted area by 2 pixels on the top and bottom and 2 pixels on the right. This
592    // is because GTK+ themes want to draw over the text box borders, but WebCore renders the inner
593    // spin button inside the text box.
594    IntRect expandedRect(rect);
595    expandedRect.inflateY(2);
596    expandedRect.setWidth(rect.width() + 2);
597
598    WidgetRenderingContext widgetContext(paintInfo.context, expandedRect);
599    GtkWidget* widget = gtkSpinButton();
600    gtk_widget_set_direction(widget, gtkTextDirection(renderObject->style()->direction()));
601
602    IntRect fullSpinButtonRect(IntPoint(), expandedRect.size());
603    widgetContext.gtkPaintBox(fullSpinButtonRect, widget, GTK_STATE_NORMAL, GTK_SHADOW_IN, "spinbutton");
604
605    bool upPressed = isSpinUpButtonPartPressed(renderObject);
606    bool upHovered = isSpinUpButtonPartHovered(renderObject);
607    bool controlActive = isEnabled(renderObject) && !isReadOnlyControl(renderObject);
608    GtkShadowType shadowType = upPressed ? GTK_SHADOW_IN : GTK_SHADOW_OUT;
609
610    GtkStateType stateType = GTK_STATE_INSENSITIVE;
611    if (controlActive) {
612        if (isPressed(renderObject) && upPressed)
613            stateType = GTK_STATE_ACTIVE;
614        else if (isHovered(renderObject) && upHovered)
615            stateType = GTK_STATE_PRELIGHT;
616        else
617            stateType = GTK_STATE_NORMAL;
618    }
619    IntRect topRect(IntPoint(), expandedRect.size());
620    topRect.setHeight(expandedRect.height() / 2);
621    widgetContext.gtkPaintBox(topRect, widget, stateType, shadowType, "spinbutton_up");
622
623    // The arrow size/position calculation here is based on the arbitrary gymnastics that happen
624    // in gtkspinbutton.c. It isn't pretty there and it isn't pretty here. This manages to make
625    // the button look native for many themes though.
626    IntRect arrowRect;
627    int arrowSize = (expandedRect.width() - 3) / 2;
628    arrowSize -= (arrowSize % 2) - 1; // Force odd.
629    arrowRect.setWidth(arrowSize);
630    arrowRect.setHeight(arrowSize);
631    arrowRect.move((expandedRect.width() - arrowRect.width()) / 2,
632                   (topRect.height() - arrowRect.height()) / 2 + 1);
633    widgetContext.gtkPaintArrow(arrowRect, widget, stateType, shadowType, GTK_ARROW_UP, "spinbutton");
634
635    shadowType = isPressed(renderObject) && !upPressed ? GTK_SHADOW_IN : GTK_SHADOW_OUT;
636    if (controlActive) {
637        if (isPressed(renderObject) && !upPressed)
638            stateType = GTK_STATE_ACTIVE;
639        else if (isHovered(renderObject) && !upHovered)
640            stateType = GTK_STATE_PRELIGHT;
641        else
642            stateType = GTK_STATE_NORMAL;
643    }
644    IntRect bottomRect(IntPoint(0, expandedRect.height() / 2), expandedRect.size());
645    bottomRect.setHeight(expandedRect.height() - bottomRect.y());
646    widgetContext.gtkPaintBox(bottomRect, widget, stateType, shadowType, "spinbutton_down");
647
648    arrowRect.setY(arrowRect.y() + bottomRect.y() - 1);
649    widgetContext.gtkPaintArrow(arrowRect, widget, stateType, shadowType, GTK_ARROW_DOWN, "spinbutton");
650
651    return false;
652}
653
654GRefPtr<GdkPixbuf> getStockIconForWidgetType(GType widgetType, const char* iconName, gint direction, gint state, gint iconSize)
655{
656    ASSERT(widgetType == GTK_TYPE_CONTAINER || widgetType == GTK_TYPE_ENTRY);
657
658    RenderThemeGtk* theme = static_cast<RenderThemeGtk*>(RenderTheme::defaultTheme().get());
659    GtkWidget* widget = widgetType == GTK_TYPE_CONTAINER ? GTK_WIDGET(theme->gtkContainer()) : theme->gtkEntry();
660
661    GtkStyle* style = gtk_widget_get_style(widget);
662    GtkIconSet* iconSet = gtk_style_lookup_icon_set(style, iconName);
663    return adoptGRef(gtk_icon_set_render_icon(iconSet, style,
664                                              static_cast<GtkTextDirection>(direction),
665                                              static_cast<GtkStateType>(state),
666                                              static_cast<GtkIconSize>(iconSize), 0, 0));
667}
668
669GRefPtr<GdkPixbuf> getStockSymbolicIconForWidgetType(GType widgetType, const char* symbolicIconName, const char *fallbackStockIconName, gint direction, gint state, gint iconSize)
670{
671    return getStockIconForWidgetType(widgetType, fallbackStockIconName, direction, state, iconSize);
672}
673
674Color RenderThemeGtk::platformActiveSelectionBackgroundColor() const
675{
676    GtkWidget* widget = gtkEntry();
677    return gtk_widget_get_style(widget)->base[GTK_STATE_SELECTED];
678}
679
680Color RenderThemeGtk::platformInactiveSelectionBackgroundColor() const
681{
682    GtkWidget* widget = gtkEntry();
683    return gtk_widget_get_style(widget)->base[GTK_STATE_ACTIVE];
684}
685
686Color RenderThemeGtk::platformActiveSelectionForegroundColor() const
687{
688    GtkWidget* widget = gtkEntry();
689    return gtk_widget_get_style(widget)->text[GTK_STATE_SELECTED];
690}
691
692Color RenderThemeGtk::platformInactiveSelectionForegroundColor() const
693{
694    GtkWidget* widget = gtkEntry();
695    return gtk_widget_get_style(widget)->text[GTK_STATE_ACTIVE];
696}
697
698Color RenderThemeGtk::activeListBoxSelectionBackgroundColor() const
699{
700    GtkWidget* widget = gtkTreeView();
701    return gtk_widget_get_style(widget)->base[GTK_STATE_SELECTED];
702}
703
704Color RenderThemeGtk::inactiveListBoxSelectionBackgroundColor() const
705{
706    GtkWidget* widget = gtkTreeView();
707    return gtk_widget_get_style(widget)->base[GTK_STATE_ACTIVE];
708}
709
710Color RenderThemeGtk::activeListBoxSelectionForegroundColor() const
711{
712    GtkWidget* widget = gtkTreeView();
713    return gtk_widget_get_style(widget)->text[GTK_STATE_SELECTED];
714}
715
716Color RenderThemeGtk::inactiveListBoxSelectionForegroundColor() const
717{
718    GtkWidget* widget = gtkTreeView();
719    return gtk_widget_get_style(widget)->text[GTK_STATE_ACTIVE];
720}
721
722Color RenderThemeGtk::systemColor(int cssValueId) const
723{
724    switch (cssValueId) {
725    case CSSValueButtontext:
726        return Color(gtk_widget_get_style(gtkButton())->fg[GTK_STATE_NORMAL]);
727    case CSSValueCaptiontext:
728        return Color(gtk_widget_get_style(gtkEntry())->fg[GTK_STATE_NORMAL]);
729    default:
730        return RenderTheme::systemColor(cssValueId);
731    }
732}
733
734static void gtkStyleSetCallback(GtkWidget* widget, GtkStyle* previous, RenderTheme* renderTheme)
735{
736    // FIXME: Make sure this function doesn't get called many times for a single GTK+ style change signal.
737    renderTheme->platformColorsDidChange();
738}
739
740static void setupWidget(GtkWidget* widget)
741{
742    gtk_widget_realize(widget);
743    g_object_set_data(G_OBJECT(widget), "transparent-bg-hint", GINT_TO_POINTER(TRUE));
744}
745
746void RenderThemeGtk::setupWidgetAndAddToContainer(GtkWidget* widget, GtkWidget* window) const
747{
748    gtk_container_add(GTK_CONTAINER(window), widget);
749    setupWidget(widget);
750
751    // FIXME: Perhaps this should only be called for the containing window or parent container.
752    g_signal_connect(widget, "style-set", G_CALLBACK(gtkStyleSetCallback), const_cast<RenderThemeGtk*>(this));
753}
754
755GtkWidget* RenderThemeGtk::gtkContainer() const
756{
757    if (m_gtkContainer)
758        return m_gtkContainer;
759
760    m_gtkWindow = gtk_window_new(GTK_WINDOW_POPUP);
761    gtk_widget_set_colormap(m_gtkWindow, m_colormap);
762    setupWidget(m_gtkWindow);
763    gtk_widget_set_name(m_gtkWindow, "MozillaGtkWidget");
764
765    m_gtkContainer = gtk_fixed_new();
766    setupWidgetAndAddToContainer(m_gtkContainer, m_gtkWindow);
767    return m_gtkContainer;
768}
769
770GtkWidget* RenderThemeGtk::gtkButton() const
771{
772    if (m_gtkButton)
773        return m_gtkButton;
774    m_gtkButton = gtk_button_new();
775    setupWidgetAndAddToContainer(m_gtkButton, gtkContainer());
776    return m_gtkButton;
777}
778
779GtkWidget* RenderThemeGtk::gtkEntry() const
780{
781    if (m_gtkEntry)
782        return m_gtkEntry;
783    m_gtkEntry = gtk_entry_new();
784    setupWidgetAndAddToContainer(m_gtkEntry, gtkContainer());
785    return m_gtkEntry;
786}
787
788GtkWidget* RenderThemeGtk::gtkTreeView() const
789{
790    if (m_gtkTreeView)
791        return m_gtkTreeView;
792    m_gtkTreeView = gtk_tree_view_new();
793    setupWidgetAndAddToContainer(m_gtkTreeView, gtkContainer());
794    return m_gtkTreeView;
795}
796
797GtkWidget* RenderThemeGtk::gtkVScale() const
798{
799    if (m_gtkVScale)
800        return m_gtkVScale;
801    m_gtkVScale = gtk_vscale_new(0);
802    setupWidgetAndAddToContainer(m_gtkVScale, gtkContainer());
803    return m_gtkVScale;
804}
805
806GtkWidget* RenderThemeGtk::gtkHScale() const
807{
808    if (m_gtkHScale)
809        return m_gtkHScale;
810    m_gtkHScale = gtk_hscale_new(0);
811    setupWidgetAndAddToContainer(m_gtkHScale, gtkContainer());
812    return m_gtkHScale;
813}
814
815GtkWidget* RenderThemeGtk::gtkRadioButton() const
816{
817    if (m_gtkRadioButton)
818        return m_gtkRadioButton;
819    m_gtkRadioButton = gtk_radio_button_new(0);
820    setupWidgetAndAddToContainer(m_gtkRadioButton, gtkContainer());
821    return m_gtkRadioButton;
822}
823
824GtkWidget* RenderThemeGtk::gtkCheckButton() const
825{
826    if (m_gtkCheckButton)
827        return m_gtkCheckButton;
828    m_gtkCheckButton = gtk_check_button_new();
829    setupWidgetAndAddToContainer(m_gtkCheckButton, gtkContainer());
830    return m_gtkCheckButton;
831}
832
833GtkWidget* RenderThemeGtk::gtkProgressBar() const
834{
835    if (m_gtkProgressBar)
836        return m_gtkProgressBar;
837    m_gtkProgressBar = gtk_progress_bar_new();
838    setupWidgetAndAddToContainer(m_gtkProgressBar, gtkContainer());
839    return m_gtkProgressBar;
840}
841
842static void getGtkComboBoxButton(GtkWidget* widget, gpointer target)
843{
844    if (!GTK_IS_TOGGLE_BUTTON(widget))
845        return;
846    GtkWidget** widgetTarget = static_cast<GtkWidget**>(target);
847    *widgetTarget = widget;
848}
849
850typedef struct {
851    GtkWidget* arrow;
852    GtkWidget* separator;
853} ComboBoxWidgetPieces;
854
855static void getGtkComboBoxPieces(GtkWidget* widget, gpointer data)
856{
857    if (GTK_IS_ARROW(widget)) {
858        static_cast<ComboBoxWidgetPieces*>(data)->arrow = widget;
859        return;
860    }
861    if (GTK_IS_SEPARATOR(widget))
862        static_cast<ComboBoxWidgetPieces*>(data)->separator = widget;
863}
864
865GtkWidget* RenderThemeGtk::gtkComboBox() const
866{
867    if (m_gtkComboBox)
868        return m_gtkComboBox;
869    m_gtkComboBox = gtk_combo_box_new();
870    setupWidgetAndAddToContainer(m_gtkComboBox, gtkContainer());
871    return m_gtkComboBox;
872}
873
874void RenderThemeGtk::refreshComboBoxChildren() const
875{
876    gtkComboBox(); // Ensure that we've initialized the combo box.
877
878    // Some themes look at widget ancestry to determine how to render widgets, so
879    // get the GtkButton that is the actual child of the combo box.
880    gtk_container_forall(GTK_CONTAINER(m_gtkComboBox), getGtkComboBoxButton, &m_gtkComboBoxButton);
881    ASSERT(m_gtkComboBoxButton);
882    setupWidget(m_gtkComboBoxButton);
883    g_object_add_weak_pointer(G_OBJECT(m_gtkComboBoxButton), reinterpret_cast<gpointer*>(&m_gtkComboBoxButton));
884
885    ComboBoxWidgetPieces pieces = { 0, 0 };
886    GtkWidget* buttonChild = gtk_bin_get_child(GTK_BIN(gtkComboBoxButton()));
887    if (GTK_IS_HBOX(buttonChild))
888        gtk_container_forall(GTK_CONTAINER(buttonChild), getGtkComboBoxPieces, &pieces);
889    else if (GTK_IS_ARROW(buttonChild))
890        pieces.arrow = buttonChild;
891
892    ASSERT(pieces.arrow);
893    m_gtkComboBoxArrow = pieces.arrow;
894    setupWidget(m_gtkComboBoxArrow);
895    // When the style changes, the combo box may destroy its children.
896    g_object_add_weak_pointer(G_OBJECT(m_gtkComboBoxArrow), reinterpret_cast<gpointer*>(&m_gtkComboBoxArrow));
897
898    m_gtkComboBoxSeparator = pieces.separator;
899    if (m_gtkComboBoxSeparator) {
900        setupWidget(m_gtkComboBoxSeparator);
901        // When the style changes, the combo box may destroy its children.
902        g_object_add_weak_pointer(G_OBJECT(m_gtkComboBoxSeparator), reinterpret_cast<gpointer*>(&m_gtkComboBoxSeparator));
903    }
904}
905
906GtkWidget* RenderThemeGtk::gtkComboBoxButton() const
907{
908    if (m_gtkComboBoxButton)
909        return m_gtkComboBoxButton;
910    refreshComboBoxChildren();
911    ASSERT(m_gtkComboBoxButton);
912    return m_gtkComboBoxButton;
913}
914
915GtkWidget* RenderThemeGtk::gtkComboBoxArrow() const
916{
917    if (m_gtkComboBoxArrow)
918        return m_gtkComboBoxArrow;
919    refreshComboBoxChildren();
920    ASSERT(m_gtkComboBoxArrow);
921    return m_gtkComboBoxArrow;
922}
923
924GtkWidget* RenderThemeGtk::gtkComboBoxSeparator() const
925{
926    // m_gtkComboBoxSeparator may be null either because we haven't initialized the combo box
927    // or because the combo boxes in this theme don't have separators. If m_gtkComboBoxArrow
928    // arrow isn't null, we definitely have initialized the combo box.
929    if (m_gtkComboBoxArrow || m_gtkComboBoxButton)
930        return m_gtkComboBoxSeparator;
931    refreshComboBoxChildren();
932    return m_gtkComboBoxSeparator;
933}
934
935GtkWidget* RenderThemeGtk::gtkHScrollbar() const
936{
937    if (m_gtkHScrollbar)
938        return m_gtkHScrollbar;
939    m_gtkHScrollbar = gtk_hscrollbar_new(0);
940    setupWidgetAndAddToContainer(m_gtkHScrollbar, gtkContainer());
941    return m_gtkHScrollbar;
942}
943
944GtkWidget* RenderThemeGtk::gtkVScrollbar() const
945{
946    if (m_gtkVScrollbar)
947        return m_gtkVScrollbar;
948    m_gtkVScrollbar = gtk_vscrollbar_new(0);
949    setupWidgetAndAddToContainer(m_gtkVScrollbar, gtkContainer());
950    return m_gtkVScrollbar;
951}
952
953GtkWidget* RenderThemeGtk::gtkSpinButton() const
954{
955    if (m_gtkSpinButton)
956        return m_gtkSpinButton;
957    m_gtkSpinButton = gtk_spin_button_new_with_range(0, 10, 1);
958    setupWidgetAndAddToContainer(m_gtkSpinButton, gtkContainer());
959    return m_gtkSpinButton;
960}
961
962} // namespace WebCore
963
964#endif // GTK_API_VERSION_2
965