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