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.
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.
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.
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.
26 #include "RenderThemeGtk.h"
28 #ifdef GTK_API_VERSION_2
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
34 #include "CSSValueKeywords.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"
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;
53 // This is not a static method, because we want to avoid having GTK+ headers in RenderThemeGtk.h.
54 extern GtkTextDirection gtkTextDirection(TextDirection);
56 void RenderThemeGtk::platformInit()
58 m_themePartsHaveRGBAColormap = true;
70 m_gtkComboBoxButton = 0;
71 m_gtkComboBoxArrow = 0;
72 m_gtkComboBoxSeparator = 0;
77 m_colormap = gdk_screen_get_rgba_colormap(gdk_screen_get_default());
79 m_themePartsHaveRGBAColormap = false;
80 m_colormap = gdk_screen_get_default_colormap(gdk_screen_get_default());
84 RenderThemeGtk::~RenderThemeGtk()
87 gtk_widget_destroy(m_gtkWindow);
91 void RenderThemeGtk::initMediaColors()
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];
100 static void adjustRectForFocus(GtkWidget* widget, FloatRect& rect, bool ignoreInteriorFocusProperty = false)
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)
110 rect.inflate(focusWidth + focusPad);
113 void RenderThemeGtk::adjustRepaintRect(const RenderObject& renderObject, FloatRect& rect)
115 ControlPart part = renderObject.style().appearance();
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);
125 case InnerSpinButtonPart:
126 // See paintInnerSpinButton for an explanation of why we expand the painting rect.
128 rect.setWidth(rect.width() + 2);
134 static GtkStateType getGtkStateType(RenderThemeGtk* theme, const RenderObject& object)
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;
145 static void setToggleSize(const RenderThemeGtk*, RenderStyle& style, GtkWidget* widget)
147 // The width and height are both specified, so we shouldn't change them.
148 if (!style.width().isIntrinsicOrAuto() && !style.height().isAuto())
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));
159 static void paintToggle(RenderThemeGtk* theme, const RenderObject& renderObject, const PaintInfo& info, const IntRect& rect, GtkWidget* widget)
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()));
168 bool indeterminate = theme->isIndeterminate(renderObject);
169 gtk_toggle_button_set_inconsistent(GTK_TOGGLE_BUTTON(widget), indeterminate);
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;
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);
185 detail = "checkbutton";
186 widgetContext.gtkPaintCheck(buttonRect, widget, toggleState, shadowType, detail);
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);
197 void RenderThemeGtk::setCheckboxSize(RenderStyle& style) const
199 setToggleSize(this, style, gtkCheckButton());
202 bool RenderThemeGtk::paintCheckbox(const RenderObject& renderObject, const PaintInfo& info, const IntRect& rect)
204 paintToggle(this, renderObject, info, rect, gtkCheckButton());
208 void RenderThemeGtk::setRadioSize(RenderStyle& style) const
210 setToggleSize(this, style, gtkRadioButton());
213 bool RenderThemeGtk::paintRadio(const RenderObject& renderObject, const PaintInfo& info, const IntRect& rect)
215 paintToggle(this, renderObject, info, rect, gtkRadioButton());
219 static void setWidgetHasFocus(GtkWidget* widget, gboolean hasFocus)
221 g_object_set(widget, "has-focus", hasFocus, NULL);
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.
226 GTK_WIDGET_SET_FLAGS(widget, GTK_HAS_FOCUS);
228 GTK_WIDGET_UNSET_FLAGS(widget, GTK_HAS_FOCUS);
231 bool RenderThemeGtk::paintButton(const RenderObject& object, const PaintInfo& info, const IntRect& rect)
233 if (info.context->paintingDisabled())
236 GtkWidget* widget = gtkButton();
237 IntRect buttonRect(IntPoint(), rect.size());
238 IntRect focusRect(buttonRect);
240 GtkStateType state = getGtkStateType(this, object);
241 gtk_widget_set_state(widget, state);
242 gtk_widget_set_direction(widget, gtkTextDirection(object.style().direction()));
244 if (isFocused(object)) {
245 setWidgetHasFocus(widget, TRUE);
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).
256 GtkStyle* style = gtk_widget_get_style(widget);
257 focusRect.inflateX(-style->xthickness - focusPadding);
258 focusRect.inflateY(-style->ythickness - focusPadding);
260 buttonRect.inflateX(-focusWidth - focusPadding);
261 buttonRect.inflateY(-focusPadding - focusPadding);
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");
271 setWidgetHasFocus(widget, FALSE);
275 int RenderThemeGtk::getComboBoxSeparatorWidth() const
277 GtkWidget* separator = gtkComboBoxSeparator();
281 gboolean hasWideSeparators = FALSE;
282 gint separatorWidth = 0;
283 gtk_widget_style_get(separator,
284 "wide-separators", &hasWideSeparators,
285 "separator-width", &separatorWidth,
287 if (hasWideSeparators)
288 return separatorWidth;
289 return gtk_widget_get_style(separator)->xthickness;
292 int RenderThemeGtk::comboBoxArrowSize(RenderStyle& style) const
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());
299 static void getButtonInnerBorder(GtkWidget* button, int& left, int& top, int& right, int& bottom)
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);
307 innerBorder = &defaultInnerBorder;
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;
314 if (innerBorder != &defaultInnerBorder)
315 gtk_border_free(innerBorder);
319 void RenderThemeGtk::getComboBoxPadding(RenderStyle& style, int& left, int& top, int& right, int& bottom) const
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)
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);
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);
335 if (style.direction() == RTL)
336 left += arrowAndSeperatorLength;
338 right += arrowAndSeperatorLength;
341 int RenderThemeGtk::popupInternalPaddingLeft(RenderStyle& style) const
343 int left = 0, top = 0, right = 0, bottom = 0;
344 getComboBoxPadding(style, left, top, right, bottom);
348 int RenderThemeGtk::popupInternalPaddingRight(RenderStyle& style) const
350 int left = 0, top = 0, right = 0, bottom = 0;
351 getComboBoxPadding(style, left, top, right, bottom);
355 int RenderThemeGtk::popupInternalPaddingTop(RenderStyle& style) const
357 int left = 0, top = 0, right = 0, bottom = 0;
358 getComboBoxPadding(style, left, top, right, bottom);
362 int RenderThemeGtk::popupInternalPaddingBottom(RenderStyle& style) const
364 int left = 0, top = 0, right = 0, bottom = 0;
365 getComboBoxPadding(style, left, top, right, bottom);
369 bool RenderThemeGtk::paintMenuList(const RenderObject& object, const PaintInfo& info, const FloatRect& r)
371 // FIXME: adopt subpixel themes.
372 IntRect rect = IntRect(r);
373 if (paintButton(object, info, rect))
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 |
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());
388 IntRect arrowRect(0, (rect.height() - arrowSize) / 2, arrowSize, arrowSize);
389 if (style.direction() == RTL)
390 arrowRect.setX(leftBorder + buttonStyle->xthickness);
392 arrowRect.setX(rect.width() - rightBorder - buttonStyle->xthickness - arrowSize);
393 GtkShadowType shadowType = isPressed(object) ? GTK_SHADOW_IN : GTK_SHADOW_OUT;
395 WidgetRenderingContext widgetContext(info.context, rect);
396 GtkStateType stateType = getGtkStateType(this, object);
397 widgetContext.gtkPaintArrow(arrowRect, gtkComboBoxArrow(), stateType, shadowType, GTK_ARROW_DOWN, "arrow");
399 // Some combo boxes do not have a separator.
400 GtkWidget* separator = gtkComboBoxSeparator();
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);
416 separatorRect.setX(arrowRect.x() - buttonStyle->xthickness - separatorWidth);
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");
423 widgetContext.gtkPaintVLine(separatorRect, separator, GTK_STATE_NORMAL, "vseparator");
428 bool RenderThemeGtk::paintTextField(const RenderObject& renderObject, const PaintInfo& info, const FloatRect& rect)
430 GtkWidget* widget = gtkEntry();
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));
438 WidgetRenderingContext widgetContext(info.context, IntRect(rect));
439 IntRect textFieldRect(IntPoint(), IntSize(rect.size()));
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");
449 // This is responsible for drawing the actual frame.
450 widgetContext.gtkPaintShadow(textFieldRect, widget, GTK_STATE_NORMAL, GTK_SHADOW_IN, "entry");
452 gboolean interiorFocus;
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");
465 widgetContext.gtkPaintFocus(textFieldRect, widget, GTK_STATE_NORMAL, "entry");
471 bool RenderThemeGtk::paintSliderTrack(const RenderObject& object, const PaintInfo& info, const IntRect& rect)
473 if (info.context->paintingDisabled())
476 ControlPart part = object.style().appearance();
477 ASSERT(part == SliderHorizontalPart || part == SliderVerticalPart || part == MediaVolumeSliderPart);
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);
486 widget = gtkVScale();
487 troughRect.inflateY(-gtk_widget_get_style(widget)->ythickness);
489 gtk_widget_set_direction(widget, gtkTextDirection(object.style().direction()));
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");
499 bool RenderThemeGtk::paintSliderThumb(const RenderObject& object, const PaintInfo& info, const IntRect& rect)
501 if (info.context->paintingDisabled())
504 ControlPart part = object.style().appearance();
505 ASSERT(part == SliderThumbHorizontalPart || part == SliderThumbVerticalPart || part == MediaVolumeSliderThumbPart);
507 GtkWidget* widget = 0;
508 const char* detail = 0;
509 GtkOrientation orientation;
510 if (part == SliderThumbHorizontalPart) {
511 widget = gtkHScale();
513 orientation = GTK_ORIENTATION_HORIZONTAL;
515 widget = gtkVScale();
517 orientation = GTK_ORIENTATION_VERTICAL;
519 gtk_widget_set_direction(widget, gtkTextDirection(object.style().direction()));
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
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);
531 void RenderThemeGtk::adjustSliderThumbSize(RenderStyle& style, Element&) const
533 ControlPart part = style.appearance();
534 if (part != SliderThumbHorizontalPart && part != SliderThumbVerticalPart)
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,
544 if (part == SliderThumbHorizontalPart) {
545 style.setWidth(Length(length, Fixed));
546 style.setHeight(Length(width, Fixed));
549 ASSERT(part == SliderThumbVerticalPart || part == MediaVolumeSliderThumbPart);
550 style.setWidth(Length(width, Fixed));
551 style.setHeight(Length(length, Fixed));
554 bool RenderThemeGtk::paintProgressBar(const RenderObject& renderObject, const PaintInfo& paintInfo, const IntRect& rect)
556 GtkWidget* widget = gtkProgressBar();
557 gtk_widget_set_direction(widget, gtkTextDirection(renderObject.style().direction()));
559 WidgetRenderingContext widgetContext(paintInfo.context, rect);
560 IntRect fullProgressBarRect(IntPoint(), rect.size());
561 widgetContext.gtkPaintBox(fullProgressBarRect, widget, GTK_STATE_NORMAL, GTK_SHADOW_IN, "trough");
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);
569 if (!progressRect.isEmpty())
570 widgetContext.gtkPaintBox(progressRect, widget, GTK_STATE_PRELIGHT, GTK_SHADOW_OUT, "bar");
575 void RenderThemeGtk::adjustInnerSpinButtonStyle(StyleResolver&, RenderStyle& style, Element&) const
577 GtkStyle* gtkStyle = gtk_widget_get_style(gtkSpinButton());
578 const PangoFontDescription* fontDescription = gtkStyle->font_desc;
579 gint fontSize = pango_font_description_get_size(fontDescription);
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;
586 style.setWidth(Length(width, Fixed));
587 style.setMinWidth(Length(width, Fixed));
590 bool RenderThemeGtk::paintInnerSpinButton(const RenderObject& renderObject, const PaintInfo& paintInfo, const IntRect& rect)
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);
599 WidgetRenderingContext widgetContext(paintInfo.context, expandedRect);
600 GtkWidget* widget = gtkSpinButton();
601 gtk_widget_set_direction(widget, gtkTextDirection(renderObject.style().direction()));
603 IntRect fullSpinButtonRect(IntPoint(), expandedRect.size());
604 widgetContext.gtkPaintBox(fullSpinButtonRect, widget, GTK_STATE_NORMAL, GTK_SHADOW_IN, "spinbutton");
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;
611 GtkStateType stateType = GTK_STATE_INSENSITIVE;
613 if (isPressed(renderObject) && upPressed)
614 stateType = GTK_STATE_ACTIVE;
615 else if (isHovered(renderObject) && upHovered)
616 stateType = GTK_STATE_PRELIGHT;
618 stateType = GTK_STATE_NORMAL;
620 IntRect topRect(IntPoint(), expandedRect.size());
621 topRect.setHeight(expandedRect.height() / 2);
622 widgetContext.gtkPaintBox(topRect, widget, stateType, shadowType, "spinbutton_up");
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.
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");
636 shadowType = isPressed(renderObject) && !upPressed ? GTK_SHADOW_IN : GTK_SHADOW_OUT;
638 if (isPressed(renderObject) && !upPressed)
639 stateType = GTK_STATE_ACTIVE;
640 else if (isHovered(renderObject) && !upHovered)
641 stateType = GTK_STATE_PRELIGHT;
643 stateType = GTK_STATE_NORMAL;
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");
649 arrowRect.setY(arrowRect.y() + bottomRect.y() - 1);
650 widgetContext.gtkPaintArrow(arrowRect, widget, stateType, shadowType, GTK_ARROW_DOWN, "spinbutton");
655 GRefPtr<GdkPixbuf> getStockIconForWidgetType(GType widgetType, const char* iconName, gint direction, gint state, gint iconSize)
657 ASSERT(widgetType == GTK_TYPE_CONTAINER || widgetType == GTK_TYPE_ENTRY);
660 RenderThemeGtk* theme = static_cast<RenderThemeGtk*>(RenderTheme::defaultTheme().get());
661 GtkWidget* widget = widgetType == GTK_TYPE_CONTAINER ? GTK_WIDGET(theme->gtkContainer()) : theme->gtkEntry();
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));
671 GRefPtr<GdkPixbuf> getStockSymbolicIconForWidgetType(GType widgetType, const char* /* symbolicIconName */, const char* fallbackStockIconName, gint direction, gint state, gint iconSize)
673 if (!fallbackStockIconName)
676 return getStockIconForWidgetType(widgetType, fallbackStockIconName, direction, state, iconSize);
679 Color RenderThemeGtk::platformActiveSelectionBackgroundColor() const
681 GtkWidget* widget = gtkEntry();
682 return gtk_widget_get_style(widget)->base[GTK_STATE_SELECTED];
685 Color RenderThemeGtk::platformInactiveSelectionBackgroundColor() const
687 GtkWidget* widget = gtkEntry();
688 return gtk_widget_get_style(widget)->base[GTK_STATE_ACTIVE];
691 Color RenderThemeGtk::platformActiveSelectionForegroundColor() const
693 GtkWidget* widget = gtkEntry();
694 return gtk_widget_get_style(widget)->text[GTK_STATE_SELECTED];
697 Color RenderThemeGtk::platformInactiveSelectionForegroundColor() const
699 GtkWidget* widget = gtkEntry();
700 return gtk_widget_get_style(widget)->text[GTK_STATE_ACTIVE];
703 Color RenderThemeGtk::platformActiveListBoxSelectionBackgroundColor() const
705 GtkWidget* widget = gtkTreeView();
706 return gtk_widget_get_style(widget)->base[GTK_STATE_SELECTED];
709 Color RenderThemeGtk::platformInactiveListBoxSelectionBackgroundColor() const
711 GtkWidget* widget = gtkTreeView();
712 return gtk_widget_get_style(widget)->base[GTK_STATE_ACTIVE];
715 Color RenderThemeGtk::platformActiveListBoxSelectionForegroundColor() const
717 GtkWidget* widget = gtkTreeView();
718 return gtk_widget_get_style(widget)->text[GTK_STATE_SELECTED];
721 Color RenderThemeGtk::platformInactiveListBoxSelectionForegroundColor() const
723 GtkWidget* widget = gtkTreeView();
724 return gtk_widget_get_style(widget)->text[GTK_STATE_ACTIVE];
727 Color RenderThemeGtk::systemColor(CSSValueID cssValueId) const
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]);
735 return RenderTheme::systemColor(cssValueId);
739 static void gtkStyleSetCallback(GtkWidget*, GtkStyle*, RenderTheme* renderTheme)
741 // FIXME: Make sure this function doesn't get called many times for a single GTK+ style change signal.
742 renderTheme->platformColorsDidChange();
745 static void setupWidget(GtkWidget* widget)
747 gtk_widget_realize(widget);
748 g_object_set_data(G_OBJECT(widget), "transparent-bg-hint", GINT_TO_POINTER(TRUE));
751 void RenderThemeGtk::setupWidgetAndAddToContainer(GtkWidget* widget, GtkWidget* window) const
753 gtk_container_add(GTK_CONTAINER(window), widget);
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));
760 GtkWidget* RenderThemeGtk::gtkContainer() const
763 return m_gtkContainer;
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");
770 m_gtkContainer = gtk_fixed_new();
771 setupWidgetAndAddToContainer(m_gtkContainer, m_gtkWindow);
772 return m_gtkContainer;
775 GtkWidget* RenderThemeGtk::gtkButton() const
779 m_gtkButton = gtk_button_new();
780 setupWidgetAndAddToContainer(m_gtkButton, gtkContainer());
784 GtkWidget* RenderThemeGtk::gtkEntry() const
788 m_gtkEntry = gtk_entry_new();
789 setupWidgetAndAddToContainer(m_gtkEntry, gtkContainer());
793 GtkWidget* RenderThemeGtk::gtkTreeView() const
796 return m_gtkTreeView;
797 m_gtkTreeView = gtk_tree_view_new();
798 setupWidgetAndAddToContainer(m_gtkTreeView, gtkContainer());
799 return m_gtkTreeView;
802 GtkWidget* RenderThemeGtk::gtkVScale() const
806 m_gtkVScale = gtk_vscale_new(0);
807 setupWidgetAndAddToContainer(m_gtkVScale, gtkContainer());
811 GtkWidget* RenderThemeGtk::gtkHScale() const
815 m_gtkHScale = gtk_hscale_new(0);
816 setupWidgetAndAddToContainer(m_gtkHScale, gtkContainer());
820 GtkWidget* RenderThemeGtk::gtkRadioButton() const
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;
829 GtkWidget* RenderThemeGtk::gtkCheckButton() const
831 if (m_gtkCheckButton)
832 return m_gtkCheckButton;
833 m_gtkCheckButton = gtk_check_button_new();
834 setupWidgetAndAddToContainer(m_gtkCheckButton, gtkContainer());
835 return m_gtkCheckButton;
838 GtkWidget* RenderThemeGtk::gtkProgressBar() const
840 if (m_gtkProgressBar)
841 return m_gtkProgressBar;
842 m_gtkProgressBar = gtk_progress_bar_new();
843 setupWidgetAndAddToContainer(m_gtkProgressBar, gtkContainer());
844 return m_gtkProgressBar;
847 static void getGtkComboBoxButton(GtkWidget* widget, gpointer target)
849 if (!GTK_IS_TOGGLE_BUTTON(widget))
851 GtkWidget** widgetTarget = static_cast<GtkWidget**>(target);
852 *widgetTarget = widget;
857 GtkWidget* separator;
858 } ComboBoxWidgetPieces;
860 static void getGtkComboBoxPieces(GtkWidget* widget, gpointer data)
862 if (GTK_IS_ARROW(widget)) {
863 static_cast<ComboBoxWidgetPieces*>(data)->arrow = widget;
866 if (GTK_IS_SEPARATOR(widget))
867 static_cast<ComboBoxWidgetPieces*>(data)->separator = widget;
870 GtkWidget* RenderThemeGtk::gtkComboBox() const
873 return m_gtkComboBox;
874 m_gtkComboBox = gtk_combo_box_new();
875 setupWidgetAndAddToContainer(m_gtkComboBox, gtkContainer());
876 return m_gtkComboBox;
879 void RenderThemeGtk::refreshComboBoxChildren() const
881 gtkComboBox(); // Ensure that we've initialized the combo box.
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));
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;
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));
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));
911 GtkWidget* RenderThemeGtk::gtkComboBoxButton() const
913 if (m_gtkComboBoxButton)
914 return m_gtkComboBoxButton;
915 refreshComboBoxChildren();
916 ASSERT(m_gtkComboBoxButton);
917 return m_gtkComboBoxButton;
920 GtkWidget* RenderThemeGtk::gtkComboBoxArrow() const
922 if (m_gtkComboBoxArrow)
923 return m_gtkComboBoxArrow;
924 refreshComboBoxChildren();
925 ASSERT(m_gtkComboBoxArrow);
926 return m_gtkComboBoxArrow;
929 GtkWidget* RenderThemeGtk::gtkComboBoxSeparator() const
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;
940 GtkWidget* RenderThemeGtk::gtkHScrollbar() const
943 return m_gtkHScrollbar;
944 m_gtkHScrollbar = gtk_hscrollbar_new(0);
945 setupWidgetAndAddToContainer(m_gtkHScrollbar, gtkContainer());
946 return m_gtkHScrollbar;
949 GtkWidget* RenderThemeGtk::gtkVScrollbar() const
952 return m_gtkVScrollbar;
953 m_gtkVScrollbar = gtk_vscrollbar_new(0);
954 setupWidgetAndAddToContainer(m_gtkVScrollbar, gtkContainer());
955 return m_gtkVScrollbar;
958 GtkWidget* RenderThemeGtk::gtkSpinButton() const
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;
967 } // namespace WebCore
969 #endif // GTK_API_VERSION_2