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