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