2010-12-15 Martin Robinson <mrobinson@igalia.com>
[WebKit-https.git] / WebCore / platform / gtk / RenderThemeGtk.cpp
1 /*
2  * Copyright (C) 2007 Apple Inc.
3  * Copyright (C) 2007 Alp Toker <alp@atoker.com>
4  * Copyright (C) 2008 Collabora Ltd.
5  * Copyright (C) 2009 Kenneth Rohde Christiansen
6  * Copyright (C) 2010 Igalia S.L.
7  *
8  * This library is free software; you can redistribute it and/or
9  * modify it under the terms of the GNU Library General Public
10  * License as published by the Free Software Foundation; either
11  * version 2 of the License, or (at your option) any later version.
12  *
13  * This library is distributed in the hope that it will be useful,
14  * but WITHOUT ANY WARRANTY; without even the implied warranty of
15  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
16  * Library General Public License for more details.
17  *
18  * You should have received a copy of the GNU Library General Public License
19  * along with this library; see the file COPYING.LIB.  If not, write to
20  * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
21  * Boston, MA 02110-1301, USA.
22  *
23  */
24
25 #include "config.h"
26 #include "RenderThemeGtk.h"
27
28 #include "AffineTransform.h"
29 #include "CSSValueKeywords.h"
30 #include "GOwnPtr.h"
31 #include "Gradient.h"
32 #include "GraphicsContext.h"
33 #include "GtkVersioning.h"
34 #include "HTMLMediaElement.h"
35 #include "HTMLNames.h"
36 #include "MediaControlElements.h"
37 #include "NotImplemented.h"
38 #include "PlatformMouseEvent.h"
39 #include "RenderBox.h"
40 #include "RenderObject.h"
41 #include "Scrollbar.h"
42 #include "TimeRanges.h"
43 #include "UserAgentStyleSheets.h"
44 #include "WidgetRenderingContext.h"
45 #include "gtkdrawing.h"
46 #include <gdk/gdk.h>
47 #include <gtk/gtk.h>
48 #include <wtf/text/CString.h>
49
50 #if ENABLE(PROGRESS_TAG)
51 #include "RenderProgress.h"
52 #endif
53
54 namespace WebCore {
55
56 using namespace HTMLNames;
57
58 static void paintStockIcon(GraphicsContext* context, const IntPoint& iconPoint, GtkStyle* style, const char* iconName,
59                            GtkTextDirection direction, GtkStateType state, GtkIconSize iconSize)
60 {
61     GtkIconSet* iconSet = gtk_style_lookup_icon_set(style, iconName);
62     PlatformRefPtr<GdkPixbuf> icon = adoptPlatformRef(gtk_icon_set_render_icon(iconSet, style, direction, state, iconSize, 0, 0));
63
64     cairo_t* cr = context->platformContext();
65     cairo_save(cr);
66     gdk_cairo_set_source_pixbuf(cr, icon.get(), iconPoint.x(), iconPoint.y());
67     cairo_paint(cr);
68     cairo_restore(cr);
69 }
70
71 #if ENABLE(VIDEO)
72 static HTMLMediaElement* getMediaElementFromRenderObject(RenderObject* o)
73 {
74     Node* node = o->node();
75     Node* mediaNode = node ? node->shadowAncestorNode() : 0;
76     if (!mediaNode || (!mediaNode->hasTagName(videoTag) && !mediaNode->hasTagName(audioTag)))
77         return 0;
78
79     return static_cast<HTMLMediaElement*>(mediaNode);
80 }
81
82 static GtkIconSize getMediaButtonIconSize(int mediaIconSize)
83 {
84     GtkIconSize iconSize = gtk_icon_size_from_name("webkit-media-button-size");
85     if (!iconSize)
86         iconSize = gtk_icon_size_register("webkit-media-button-size", mediaIconSize, mediaIconSize);
87     return iconSize;
88 }
89
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
98 void RenderThemeGtk::initMediaButtons()
99 {
100     static bool iconsInitialized = false;
101
102     if (iconsInitialized)
103         return;
104
105     PlatformRefPtr<GtkIconFactory> iconFactory = adoptPlatformRef(gtk_icon_factory_new());
106     GtkIconSource* iconSource = gtk_icon_source_new();
107     const char* icons[] = { "audio-volume-high", "audio-volume-muted" };
108
109     gtk_icon_factory_add_default(iconFactory.get());
110
111     for (size_t i = 0; i < G_N_ELEMENTS(icons); ++i) {
112         gtk_icon_source_set_icon_name(iconSource, icons[i]);
113         GtkIconSet* iconSet = gtk_icon_set_new();
114         gtk_icon_set_add_source(iconSet, iconSource);
115         gtk_icon_factory_add(iconFactory.get(), icons[i], iconSet);
116         gtk_icon_set_unref(iconSet);
117     }
118
119     gtk_icon_source_free(iconSource);
120
121     iconsInitialized = true;
122 }
123 #endif
124
125 PassRefPtr<RenderTheme> RenderThemeGtk::create()
126 {
127     return adoptRef(new RenderThemeGtk());
128 }
129
130 PassRefPtr<RenderTheme> RenderTheme::themeForPage(Page* page)
131 {
132     static RenderTheme* rt = RenderThemeGtk::create().releaseRef();
133     return rt;
134 }
135
136 static int mozGtkRefCount = 0;
137
138 RenderThemeGtk::RenderThemeGtk()
139     : m_gtkWindow(0)
140     , m_gtkContainer(0)
141     , m_gtkButton(0)
142     , m_gtkEntry(0)
143     , m_gtkTreeView(0)
144     , m_panelColor(Color::white)
145     , m_sliderColor(Color::white)
146     , m_sliderThumbColor(Color::white)
147     , m_mediaIconSize(16)
148     , m_mediaSliderHeight(14)
149     , m_mediaSliderThumbWidth(12)
150     , m_mediaSliderThumbHeight(12)
151 #ifdef GTK_API_VERSION_2
152     , m_themePartsHaveRGBAColormap(true)
153 #endif
154 {
155
156     memset(&m_themeParts, 0, sizeof(GtkThemeParts));
157 #ifdef GTK_API_VERSION_2
158     GdkColormap* colormap = gdk_screen_get_rgba_colormap(gdk_screen_get_default());
159     if (!colormap) {
160         m_themePartsHaveRGBAColormap = false;
161         colormap = gdk_screen_get_default_colormap(gdk_screen_get_default());
162     }
163     m_themeParts.colormap = colormap;
164 #endif
165
166     // Initialize the Mozilla theme drawing code.
167     if (!mozGtkRefCount) {
168         moz_gtk_init();
169         moz_gtk_use_theme_parts(&m_themeParts);
170     }
171     ++mozGtkRefCount;
172
173 #if ENABLE(VIDEO)
174     initMediaColors();
175     initMediaButtons();
176 #endif
177 }
178
179 RenderThemeGtk::~RenderThemeGtk()
180 {
181     --mozGtkRefCount;
182
183     if (!mozGtkRefCount)
184         moz_gtk_shutdown();
185
186     gtk_widget_destroy(m_gtkWindow);
187 }
188
189 void RenderThemeGtk::getIndicatorMetrics(ControlPart part, int& indicatorSize, int& indicatorSpacing) const
190 {
191     ASSERT(part == CheckboxPart || part == RadioPart);
192     if (part == CheckboxPart) {
193         moz_gtk_checkbox_get_metrics(&indicatorSize, &indicatorSpacing);
194         return;
195     }
196
197     // RadioPart
198     moz_gtk_radio_get_metrics(&indicatorSize, &indicatorSpacing);
199 }
200
201 static bool supportsFocus(ControlPart appearance)
202 {
203     switch (appearance) {
204     case PushButtonPart:
205     case ButtonPart:
206     case TextFieldPart:
207     case TextAreaPart:
208     case SearchFieldPart:
209     case MenulistPart:
210     case RadioPart:
211     case CheckboxPart:
212     case SliderHorizontalPart:
213     case SliderVerticalPart:
214         return true;
215     default:
216         return false;
217     }
218 }
219
220 GtkStateType RenderThemeGtk::getGtkStateType(RenderObject* object)
221 {
222     if (!isEnabled(object) || isReadOnlyControl(object))
223         return GTK_STATE_INSENSITIVE;
224     if (isPressed(object))
225         return GTK_STATE_ACTIVE;
226     if (isHovered(object))
227         return GTK_STATE_PRELIGHT;
228     return GTK_STATE_NORMAL;
229 }
230
231 bool RenderThemeGtk::supportsFocusRing(const RenderStyle* style) const
232 {
233     return supportsFocus(style->appearance());
234 }
235
236 bool RenderThemeGtk::controlSupportsTints(const RenderObject* o) const
237 {
238     return isEnabled(o);
239 }
240
241 int RenderThemeGtk::baselinePosition(const RenderObject* o) const
242 {
243     if (!o->isBox())
244         return 0;
245
246     // FIXME: This strategy is possibly incorrect for the GTK+ port.
247     if (o->style()->appearance() == CheckboxPart
248         || o->style()->appearance() == RadioPart) {
249         const RenderBox* box = toRenderBox(o);
250         return box->marginTop() + box->height() - 2;
251     }
252
253     return RenderTheme::baselinePosition(o);
254 }
255
256 static GtkTextDirection gtkTextDirection(TextDirection direction)
257 {
258     switch (direction) {
259     case RTL:
260         return GTK_TEXT_DIR_RTL;
261     case LTR:
262         return GTK_TEXT_DIR_LTR;
263     default:
264         return GTK_TEXT_DIR_NONE;
265     }
266 }
267
268 GtkStateType RenderThemeGtk::gtkIconState(RenderObject* renderObject)
269 {
270     if (!isEnabled(renderObject))
271         return GTK_STATE_INSENSITIVE;
272     if (isPressed(renderObject))
273         return GTK_STATE_ACTIVE;
274     if (isHovered(renderObject))
275         return GTK_STATE_PRELIGHT;
276
277     return GTK_STATE_NORMAL;
278 }
279
280 bool RenderThemeGtk::paintRenderObject(GtkThemeWidgetType type, RenderObject* renderObject, GraphicsContext* context, const IntRect& rect, int flags)
281 {
282     // Painting is disabled so just claim to have succeeded
283     if (context->paintingDisabled())
284         return false;
285
286     GtkWidgetState widgetState;
287     widgetState.active = isPressed(renderObject);
288     widgetState.focused = isFocused(renderObject);
289
290     // https://bugs.webkit.org/show_bug.cgi?id=18364
291     // The Mozilla theme drawing code, only paints a button as pressed when it's pressed 
292     // while hovered. Until we move away from the Mozila code, work-around the issue by
293     // forcing a pressed button into the hovered state. This ensures that buttons activated
294     // via the keyboard have the proper rendering.
295     widgetState.inHover = isHovered(renderObject) || (type == MOZ_GTK_BUTTON && isPressed(renderObject));
296
297     // FIXME: Disabled does not always give the correct appearance for ReadOnly
298     widgetState.disabled = !isEnabled(renderObject) || isReadOnlyControl(renderObject);
299     widgetState.isDefault = false;
300     widgetState.canDefault = false;
301
302     WidgetRenderingContext widgetContext(context, rect);
303     return !widgetContext.paintMozillaWidget(type, &widgetState, flags, gtkTextDirection(renderObject->style()->direction()));
304 }
305
306 static void setToggleSize(const RenderThemeGtk* theme, RenderStyle* style, ControlPart appearance)
307 {
308     // The width and height are both specified, so we shouldn't change them.
309     if (!style->width().isIntrinsicOrAuto() && !style->height().isAuto())
310         return;
311
312     // FIXME: This is probably not correct use of indicatorSize and indicatorSpacing.
313     gint indicatorSize, indicatorSpacing;
314     theme->getIndicatorMetrics(appearance, indicatorSize, indicatorSpacing);
315
316     // Other ports hard-code this to 13, but GTK+ users tend to demand the native look.
317     // It could be made a configuration option values other than 13 actually break site compatibility.
318     int length = indicatorSize + indicatorSpacing;
319     if (style->width().isIntrinsicOrAuto())
320         style->setWidth(Length(length, Fixed));
321
322     if (style->height().isAuto())
323         style->setHeight(Length(length, Fixed));
324 }
325
326 void RenderThemeGtk::setCheckboxSize(RenderStyle* style) const
327 {
328     setToggleSize(this, style, RadioPart);
329 }
330
331 bool RenderThemeGtk::paintCheckbox(RenderObject* o, const PaintInfo& i, const IntRect& rect)
332 {
333     return paintRenderObject(MOZ_GTK_CHECKBUTTON, o, i.context, rect, isChecked(o));
334 }
335
336 void RenderThemeGtk::setRadioSize(RenderStyle* style) const
337 {
338     setToggleSize(this, style, RadioPart);
339 }
340
341 bool RenderThemeGtk::paintRadio(RenderObject* o, const PaintInfo& i, const IntRect& rect)
342 {
343     return paintRenderObject(MOZ_GTK_RADIOBUTTON, o, i.context, rect, isChecked(o));
344 }
345
346 void RenderThemeGtk::adjustButtonStyle(CSSStyleSelector* selector, RenderStyle* style, WebCore::Element* e) const
347 {
348     // Some layout tests check explicitly that buttons ignore line-height.
349     if (style->appearance() == PushButtonPart)
350         style->setLineHeight(RenderStyle::initialLineHeight());
351 }
352
353 bool RenderThemeGtk::paintButton(RenderObject* object, const PaintInfo& info, const IntRect& rect)
354 {
355     if (info.context->paintingDisabled())
356         return false;
357
358     GtkWidget* widget = gtkButton();
359     IntRect buttonRect(IntPoint(), rect.size());
360     IntRect focusRect(buttonRect);
361
362     GtkStateType state = getGtkStateType(object);
363     gtk_widget_set_state(widget, state);
364     gtk_widget_set_direction(widget, gtkTextDirection(object->style()->direction()));
365
366     if (isFocused(object)) {
367         if (isEnabled(object)) {
368 #if !GTK_CHECK_VERSION(2, 22, 0)
369             GTK_WIDGET_SET_FLAGS(widget, GTK_HAS_FOCUS);
370 #endif
371             g_object_set(widget, "has-focus", TRUE, NULL);
372         }
373
374         gboolean interiorFocus = 0, focusWidth = 0, focusPadding = 0;
375         gtk_widget_style_get(widget,
376                              "interior-focus", &interiorFocus,
377                              "focus-line-width", &focusWidth,
378                              "focus-padding", &focusPadding, NULL);
379         // If we are using exterior focus, we shrink the button rect down before
380         // drawing. If we are using interior focus we shrink the focus rect. This
381         // approach originates from the Mozilla theme drawing code (gtk2drawing.c).
382         if (interiorFocus) {
383             GtkStyle* style = gtk_widget_get_style(widget);
384             focusRect.inflateX(-style->xthickness - focusPadding);
385             focusRect.inflateY(-style->ythickness - focusPadding);
386         } else {
387             buttonRect.inflateX(-focusWidth - focusPadding);
388             buttonRect.inflateY(-focusPadding - focusPadding);
389         }
390     }
391
392     WidgetRenderingContext widgetContext(info.context, rect);
393     GtkShadowType shadowType = state == GTK_STATE_ACTIVE ? GTK_SHADOW_IN : GTK_SHADOW_OUT;
394     widgetContext.gtkPaintBox(buttonRect, widget, state, shadowType, "button");
395     if (isFocused(object))
396         widgetContext.gtkPaintFocus(focusRect, widget, state, "button");
397
398 #if !GTK_CHECK_VERSION(2, 22, 0)
399     GTK_WIDGET_UNSET_FLAGS(widget, GTK_HAS_FOCUS);
400 #endif
401     g_object_set(widget, "has-focus", FALSE, NULL);
402     return false;
403 }
404
405 static void getComboBoxPadding(RenderStyle* style, int& left, int& top, int& right, int& bottom)
406 {
407     // If this menu list button isn't drawn using the native theme, we
408     // don't add any extra padding beyond what WebCore already uses.
409     if (style->appearance() == NoControlPart)
410         return;
411     moz_gtk_get_widget_border(MOZ_GTK_DROPDOWN, &left, &top, &right, &bottom,
412                               gtkTextDirection(style->direction()), TRUE);
413 }
414
415 int RenderThemeGtk::popupInternalPaddingLeft(RenderStyle* style) const
416 {
417     int left = 0, top = 0, right = 0, bottom = 0;
418     getComboBoxPadding(style, left, top, right, bottom);
419     return left;
420 }
421
422 int RenderThemeGtk::popupInternalPaddingRight(RenderStyle* style) const
423 {
424     int left = 0, top = 0, right = 0, bottom = 0;
425     getComboBoxPadding(style, left, top, right, bottom);
426     return right;
427 }
428
429 int RenderThemeGtk::popupInternalPaddingTop(RenderStyle* style) const
430 {
431     int left = 0, top = 0, right = 0, bottom = 0;
432     getComboBoxPadding(style, left, top, right, bottom);
433     return top;
434 }
435
436 int RenderThemeGtk::popupInternalPaddingBottom(RenderStyle* style) const
437 {
438     int left = 0, top = 0, right = 0, bottom = 0;
439     getComboBoxPadding(style, left, top, right, bottom);
440     return bottom;
441 }
442
443 void RenderThemeGtk::adjustMenuListStyle(CSSStyleSelector* selector, RenderStyle* style, Element* e) const
444 {
445     // The tests check explicitly that select menu buttons ignore line height.
446     style->setLineHeight(RenderStyle::initialLineHeight());
447
448     // We cannot give a proper rendering when border radius is active, unfortunately.
449     style->resetBorderRadius();
450 }
451
452 void RenderThemeGtk::adjustMenuListButtonStyle(CSSStyleSelector* selector, RenderStyle* style, Element* e) const
453 {
454     adjustMenuListStyle(selector, style, e);
455 }
456
457 bool RenderThemeGtk::paintMenuList(RenderObject* o, const PaintInfo& i, const IntRect& rect)
458 {
459     return paintRenderObject(MOZ_GTK_DROPDOWN, o, i.context, rect);
460 }
461
462 bool RenderThemeGtk::paintMenuListButton(RenderObject* object, const PaintInfo& info, const IntRect& rect)
463 {
464     return paintMenuList(object, info, rect);
465 }
466
467 static void setTextInputBorders(RenderStyle* style)
468 {
469     // If this control isn't drawn using the native theme, we don't touch the borders.
470     if (style->appearance() == NoControlPart)
471         return;
472
473     // We cannot give a proper rendering when border radius is active, unfortunately.
474     style->resetBorderRadius();
475
476     int left = 0, top = 0, right = 0, bottom = 0;
477     moz_gtk_get_widget_border(MOZ_GTK_ENTRY, &left, &top, &right, &bottom,
478                               gtkTextDirection(style->direction()), TRUE);
479     style->setBorderLeftWidth(left);
480     style->setBorderTopWidth(top);
481     style->setBorderRightWidth(right);
482     style->setBorderBottomWidth(bottom);
483 }
484
485 void RenderThemeGtk::adjustTextFieldStyle(CSSStyleSelector* selector, RenderStyle* style, Element* e) const
486 {
487     setTextInputBorders(style);
488 }
489
490 bool RenderThemeGtk::paintTextField(RenderObject* o, const PaintInfo& i, const IntRect& rect)
491 {
492     return paintRenderObject(MOZ_GTK_ENTRY, o, i.context, rect);
493 }
494
495 void RenderThemeGtk::adjustTextAreaStyle(CSSStyleSelector* selector, RenderStyle* style, Element* e) const
496 {
497     setTextInputBorders(style);
498 }
499
500 bool RenderThemeGtk::paintTextArea(RenderObject* o, const PaintInfo& i, const IntRect& r)
501 {
502     return paintTextField(o, i, r);
503 }
504
505 void RenderThemeGtk::adjustSearchFieldResultsButtonStyle(CSSStyleSelector* selector, RenderStyle* style, Element* e) const
506 {
507     adjustSearchFieldCancelButtonStyle(selector, style, e);
508 }
509
510 bool RenderThemeGtk::paintSearchFieldResultsButton(RenderObject* o, const PaintInfo& i, const IntRect& rect)
511 {
512     return paintSearchFieldResultsDecoration(o, i, rect);
513 }
514
515 void RenderThemeGtk::adjustSearchFieldResultsDecorationStyle(CSSStyleSelector* selector, RenderStyle* style, Element* e) const
516 {
517     style->resetBorder();
518     style->resetPadding();
519
520     gint width = 0, height = 0;
521     gtk_icon_size_lookup(GTK_ICON_SIZE_MENU, &width, &height);
522     style->setWidth(Length(width, Fixed));
523     style->setHeight(Length(height, Fixed));
524 }
525
526 static IntPoint centerRectVerticallyInParentInputElement(RenderObject* object, const IntRect& rect)
527 {
528     Node* input = object->node()->shadowAncestorNode(); // Get the renderer of <input> element.
529     if (!input->renderer()->isBox())
530         return rect.topLeft();
531
532     // If possible center the y-coordinate of the rect vertically in the parent input element.
533     // We also add one pixel here to ensure that the y coordinate is rounded up for box heights
534     // that are even, which looks in relation to the box text.
535     IntRect inputContentBox = toRenderBox(input->renderer())->absoluteContentBox();
536
537     return IntPoint(rect.x(), inputContentBox.y() + (inputContentBox.height() - rect.height() + 1) / 2);
538 }
539
540 bool RenderThemeGtk::paintSearchFieldResultsDecoration(RenderObject* renderObject, const PaintInfo& paintInfo, const IntRect& rect)
541 {
542     GtkStyle* style = gtk_widget_get_style(GTK_WIDGET(gtkEntry()));
543     IntPoint iconPoint(centerRectVerticallyInParentInputElement(renderObject, rect));
544     paintStockIcon(paintInfo.context, iconPoint, style, GTK_STOCK_FIND,
545                    gtkTextDirection(renderObject->style()->direction()),
546                    gtkIconState(renderObject), GTK_ICON_SIZE_MENU);
547     return false;
548 }
549
550 void RenderThemeGtk::adjustSearchFieldCancelButtonStyle(CSSStyleSelector* selector, RenderStyle* style, Element* e) const
551 {
552     style->resetBorder();
553     style->resetPadding();
554
555     gint width = 0, height = 0;
556     gtk_icon_size_lookup(GTK_ICON_SIZE_MENU, &width, &height);
557     style->setWidth(Length(width, Fixed));
558     style->setHeight(Length(height, Fixed));
559 }
560
561 bool RenderThemeGtk::paintSearchFieldCancelButton(RenderObject* renderObject, const PaintInfo& paintInfo, const IntRect& rect)
562 {
563     GtkStyle* style = gtk_widget_get_style(GTK_WIDGET(gtkEntry()));
564     IntPoint iconPoint(centerRectVerticallyInParentInputElement(renderObject, rect));
565     paintStockIcon(paintInfo.context, iconPoint, style, GTK_STOCK_CLEAR,
566                    gtkTextDirection(renderObject->style()->direction()),
567                    gtkIconState(renderObject), GTK_ICON_SIZE_MENU);
568     return false;
569 }
570
571 void RenderThemeGtk::adjustSearchFieldStyle(CSSStyleSelector* selector, RenderStyle* style, Element* e) const
572 {
573     style->setLineHeight(RenderStyle::initialLineHeight());
574     setTextInputBorders(style);
575 }
576
577 bool RenderThemeGtk::paintSearchField(RenderObject* o, const PaintInfo& i, const IntRect& rect)
578 {
579     return paintTextField(o, i, rect);
580 }
581
582 bool RenderThemeGtk::paintSliderTrack(RenderObject* object, const PaintInfo& info, const IntRect& rect)
583 {
584     if (info.context->paintingDisabled())
585         return false;
586
587     ControlPart part = object->style()->appearance();
588     ASSERT(part == SliderHorizontalPart || part == SliderVerticalPart);
589
590     // We shrink the trough rect slightly to make room for the focus indicator.
591     IntRect troughRect(IntPoint(), rect.size()); // This is relative to rect.
592     GtkWidget* widget = 0;
593     if (part == SliderVerticalPart) {
594         widget = gtkVScale();
595         troughRect.inflateY(-gtk_widget_get_style(widget)->ythickness);
596     } else {
597         widget = gtkHScale();
598         troughRect.inflateX(-gtk_widget_get_style(widget)->xthickness);
599     }
600     gtk_widget_set_direction(widget, gtkTextDirection(object->style()->direction()));
601
602     WidgetRenderingContext widgetContext(info.context, rect);
603     widgetContext.gtkPaintBox(troughRect, widget, GTK_STATE_ACTIVE, GTK_SHADOW_OUT, "trough");
604     if (isFocused(object))
605         widgetContext.gtkPaintFocus(IntRect(IntPoint(), rect.size()), widget, getGtkStateType(object), "trough");
606
607     return false;
608 }
609
610 void RenderThemeGtk::adjustSliderTrackStyle(CSSStyleSelector*, RenderStyle* style, Element*) const
611 {
612     style->setBoxShadow(0);
613 }
614
615 bool RenderThemeGtk::paintSliderThumb(RenderObject* object, const PaintInfo& info, const IntRect& rect)
616 {
617     if (info.context->paintingDisabled())
618         return false;
619
620     ControlPart part = object->style()->appearance();
621     ASSERT(part == SliderThumbHorizontalPart || part == SliderThumbVerticalPart);
622
623     GtkWidget* widget = 0;
624     const char* detail = 0;
625     GtkOrientation orientation;
626     if (part == SliderThumbVerticalPart) {
627         widget = gtkVScale();
628         detail = "vscale";
629         orientation = GTK_ORIENTATION_VERTICAL;
630     } else {
631         widget = gtkHScale();
632         detail = "hscale";
633         orientation = GTK_ORIENTATION_HORIZONTAL;
634     }
635     gtk_widget_set_direction(widget, gtkTextDirection(object->style()->direction()));
636
637     // Only some themes have slider thumbs respond to clicks and some don't. This information is
638     // gathered via the 'activate-slider' property, but it's deprecated in GTK+ 2.22 and removed in
639     // GTK+ 3.x. The drawback of not honoring it is that slider thumbs change color when you click
640     // on them. 
641     IntRect thumbRect(IntPoint(), rect.size());
642     WidgetRenderingContext widgetContext(info.context, rect);
643     widgetContext.gtkPaintSlider(thumbRect, widget, getGtkStateType(object), GTK_SHADOW_OUT, detail, orientation);
644     return false;
645 }
646
647 void RenderThemeGtk::adjustSliderThumbStyle(CSSStyleSelector*, RenderStyle* style, Element*) const
648 {
649     style->setBoxShadow(0);
650 }
651
652 void RenderThemeGtk::adjustSliderThumbSize(RenderObject* o) const
653 {
654     ControlPart part = o->style()->appearance();
655 #if ENABLE(VIDEO)
656     if (part == MediaSliderThumbPart) {
657         o->style()->setWidth(Length(m_mediaSliderThumbWidth, Fixed));
658         o->style()->setHeight(Length(m_mediaSliderThumbHeight, Fixed));
659         return;
660     }
661     if (part == MediaVolumeSliderThumbPart)
662         return;
663 #endif
664
665     GtkWidget* widget = part == SliderThumbHorizontalPart ? gtkHScale() : gtkVScale();
666     int length = 0, width = 0;
667     gtk_widget_style_get(widget,
668                          "slider_length", &length,
669                          "slider_width", &width,
670                          NULL);
671
672     if (part == SliderThumbHorizontalPart) {
673         o->style()->setWidth(Length(length, Fixed));
674         o->style()->setHeight(Length(width, Fixed));
675         return;
676     }
677     ASSERT(part == SliderThumbVerticalPart);
678     o->style()->setWidth(Length(width, Fixed));
679     o->style()->setHeight(Length(length, Fixed));
680 }
681
682 Color RenderThemeGtk::platformActiveSelectionBackgroundColor() const
683 {
684     GtkWidget* widget = gtkEntry();
685     return gtk_widget_get_style(widget)->base[GTK_STATE_SELECTED];
686 }
687
688 Color RenderThemeGtk::platformInactiveSelectionBackgroundColor() const
689 {
690     GtkWidget* widget = gtkEntry();
691     return gtk_widget_get_style(widget)->base[GTK_STATE_ACTIVE];
692 }
693
694 Color RenderThemeGtk::platformActiveSelectionForegroundColor() const
695 {
696     GtkWidget* widget = gtkEntry();
697     return gtk_widget_get_style(widget)->text[GTK_STATE_SELECTED];
698 }
699
700 Color RenderThemeGtk::platformInactiveSelectionForegroundColor() const
701 {
702     GtkWidget* widget = gtkEntry();
703     return gtk_widget_get_style(widget)->text[GTK_STATE_ACTIVE];
704 }
705
706 Color RenderThemeGtk::activeListBoxSelectionBackgroundColor() const
707 {
708     GtkWidget* widget = gtkTreeView();
709     return gtk_widget_get_style(widget)->base[GTK_STATE_SELECTED];
710 }
711
712 Color RenderThemeGtk::inactiveListBoxSelectionBackgroundColor() const
713 {
714     GtkWidget* widget = gtkTreeView();
715     return gtk_widget_get_style(widget)->base[GTK_STATE_ACTIVE];
716 }
717
718 Color RenderThemeGtk::activeListBoxSelectionForegroundColor() const
719 {
720     GtkWidget* widget = gtkTreeView();
721     return gtk_widget_get_style(widget)->text[GTK_STATE_SELECTED];
722 }
723
724 Color RenderThemeGtk::inactiveListBoxSelectionForegroundColor() const
725 {
726     GtkWidget* widget = gtkTreeView();
727     return gtk_widget_get_style(widget)->text[GTK_STATE_ACTIVE];
728 }
729
730 double RenderThemeGtk::caretBlinkInterval() const
731 {
732     GtkSettings* settings = gtk_settings_get_default();
733
734     gboolean shouldBlink;
735     gint time;
736
737     g_object_get(settings, "gtk-cursor-blink", &shouldBlink, "gtk-cursor-blink-time", &time, NULL);
738
739     if (!shouldBlink)
740         return 0;
741
742     return time / 2000.;
743 }
744
745 static double getScreenDPI()
746 {
747     // FIXME: Really this should be the widget's screen.
748     GdkScreen* screen = gdk_screen_get_default();
749     if (!screen)
750         return 96; // Default to 96 DPI.
751
752     float dpi = gdk_screen_get_resolution(screen);
753     if (dpi <= 0)
754         return 96;
755     return dpi;
756 }
757
758 void RenderThemeGtk::systemFont(int, FontDescription& fontDescription) const
759 {
760     GtkSettings* settings = gtk_settings_get_default();
761     if (!settings)
762         return;
763
764     // This will be a font selection string like "Sans 10" so we cannot use it as the family name.
765     GOwnPtr<gchar> fontName;
766     g_object_get(settings, "gtk-font-name", &fontName.outPtr(), NULL);
767
768     PangoFontDescription* pangoDescription = pango_font_description_from_string(fontName.get());
769     if (!pangoDescription)
770         return;
771
772     fontDescription.firstFamily().setFamily(pango_font_description_get_family(pangoDescription));
773
774     int size = pango_font_description_get_size(pangoDescription) / PANGO_SCALE;
775     // If the size of the font is in points, we need to convert it to pixels.
776     if (!pango_font_description_get_size_is_absolute(pangoDescription))
777         size = size * (getScreenDPI() / 72.0);
778
779     fontDescription.setSpecifiedSize(size);
780     fontDescription.setIsAbsoluteSize(true);
781     fontDescription.setGenericFamily(FontDescription::NoFamily);
782     fontDescription.setWeight(FontWeightNormal);
783     fontDescription.setItalic(false);
784     pango_font_description_free(pangoDescription);
785 }
786
787 Color RenderThemeGtk::systemColor(int cssValueId) const
788 {
789     switch (cssValueId) {
790     case CSSValueButtontext:
791         return Color(gtk_widget_get_style(gtkButton())->fg[GTK_STATE_NORMAL]);
792     case CSSValueCaptiontext:
793         return Color(gtk_widget_get_style(gtkEntry())->fg[GTK_STATE_NORMAL]);
794     default:
795         return RenderTheme::systemColor(cssValueId);
796     }
797 }
798
799 static void gtkStyleSetCallback(GtkWidget* widget, GtkStyle* previous, RenderTheme* renderTheme)
800 {
801     // FIXME: Make sure this function doesn't get called many times for a single GTK+ style change signal.
802     renderTheme->platformColorsDidChange();
803 }
804
805 void RenderThemeGtk::setupWidgetAndAddToContainer(GtkWidget* widget, GtkWidget* window) const
806 {
807     gtk_container_add(GTK_CONTAINER(window), widget);
808     gtk_widget_realize(widget);
809     g_object_set_data(G_OBJECT(widget), "transparent-bg-hint", GINT_TO_POINTER(TRUE));
810
811     // FIXME: Perhaps this should only be called for the containing window or parent container.
812     g_signal_connect(widget, "style-set", G_CALLBACK(gtkStyleSetCallback), const_cast<RenderThemeGtk*>(this));
813 }
814
815 GtkWidget* RenderThemeGtk::gtkContainer() const
816 {
817     if (m_gtkContainer)
818         return m_gtkContainer;
819
820     m_gtkWindow = gtk_window_new(GTK_WINDOW_POPUP);
821 #if GTK_API_VERSION_2
822     gtk_widget_set_colormap(m_gtkWindow, m_themeParts.colormap);
823 #endif
824     gtk_widget_realize(m_gtkWindow);
825     gtk_widget_set_name(m_gtkWindow, "MozillaGtkWidget");
826
827     m_gtkContainer = gtk_fixed_new();
828     setupWidgetAndAddToContainer(m_gtkContainer, m_gtkWindow);
829     return m_gtkContainer;
830 }
831
832 GtkWidget* RenderThemeGtk::gtkButton() const
833 {
834     if (m_gtkButton)
835         return m_gtkButton;
836     m_gtkButton = gtk_button_new();
837     setupWidgetAndAddToContainer(m_gtkButton, gtkContainer());
838     return m_gtkButton;
839 }
840
841 GtkWidget* RenderThemeGtk::gtkEntry() const
842 {
843     if (m_gtkEntry)
844         return m_gtkEntry;
845     m_gtkEntry = gtk_entry_new();
846     setupWidgetAndAddToContainer(m_gtkEntry, gtkContainer());
847     return m_gtkEntry;
848 }
849
850 GtkWidget* RenderThemeGtk::gtkTreeView() const
851 {
852     if (m_gtkTreeView)
853         return m_gtkTreeView;
854     m_gtkTreeView = gtk_tree_view_new();
855     setupWidgetAndAddToContainer(m_gtkTreeView, gtkContainer());
856     return m_gtkTreeView;
857 }
858
859 GtkWidget* RenderThemeGtk::gtkVScale() const
860 {
861     if (m_gtkVScale)
862         return m_gtkVScale;
863     m_gtkVScale = gtk_vscale_new(0);
864     setupWidgetAndAddToContainer(m_gtkVScale, gtkContainer());
865     return m_gtkVScale;
866 }
867
868 GtkWidget* RenderThemeGtk::gtkHScale() const
869 {
870     if (m_gtkHScale)
871         return m_gtkHScale;
872     m_gtkHScale = gtk_hscale_new(0);
873     setupWidgetAndAddToContainer(m_gtkHScale, gtkContainer());
874     return m_gtkHScale;
875 }
876
877 GtkWidget* RenderThemeGtk::gtkScrollbar()
878 {
879     return moz_gtk_get_scrollbar_widget();
880 }
881
882 void RenderThemeGtk::platformColorsDidChange()
883 {
884 #if ENABLE(VIDEO)
885     initMediaColors();
886 #endif
887     RenderTheme::platformColorsDidChange();
888 }
889
890 #if ENABLE(VIDEO)
891 String RenderThemeGtk::extraMediaControlsStyleSheet()
892 {
893     return String(mediaControlsGtkUserAgentStyleSheet, sizeof(mediaControlsGtkUserAgentStyleSheet));
894 }
895
896 bool RenderThemeGtk::paintMediaButton(RenderObject* renderObject, GraphicsContext* context, const IntRect& rect, const char* iconName)
897 {
898     GtkStyle* style = gtk_widget_get_style(GTK_WIDGET(gtkContainer()));
899     IntPoint iconPoint(rect.x() + (rect.width() - m_mediaIconSize) / 2,
900                        rect.y() + (rect.height() - m_mediaIconSize) / 2);
901     context->fillRect(FloatRect(rect), m_panelColor, ColorSpaceDeviceRGB);
902     paintStockIcon(context, iconPoint, style, iconName, gtkTextDirection(renderObject->style()->direction()),
903                    gtkIconState(renderObject), getMediaButtonIconSize(m_mediaIconSize));
904
905     return false;
906 }
907
908 bool RenderThemeGtk::paintMediaFullscreenButton(RenderObject* renderObject, const PaintInfo& paintInfo, const IntRect& rect)
909 {
910     return paintMediaButton(renderObject, paintInfo.context, rect, GTK_STOCK_FULLSCREEN);
911 }
912
913 bool RenderThemeGtk::paintMediaMuteButton(RenderObject* renderObject, const PaintInfo& paintInfo, const IntRect& rect)
914 {
915     HTMLMediaElement* mediaElement = getMediaElementFromRenderObject(renderObject);
916     if (!mediaElement)
917         return false;
918
919     return paintMediaButton(renderObject, paintInfo.context, rect, mediaElement->muted() ? "audio-volume-muted" : "audio-volume-high");
920 }
921
922 bool RenderThemeGtk::paintMediaPlayButton(RenderObject* renderObject, const PaintInfo& paintInfo, const IntRect& rect)
923 {
924     Node* node = renderObject->node();
925     if (!node)
926         return false;
927
928     MediaControlPlayButtonElement* button = static_cast<MediaControlPlayButtonElement*>(node);
929     return paintMediaButton(renderObject, paintInfo.context, rect, button->displayType() == MediaPlayButton ? GTK_STOCK_MEDIA_PLAY : GTK_STOCK_MEDIA_PAUSE);
930 }
931
932 bool RenderThemeGtk::paintMediaSeekBackButton(RenderObject* renderObject, const PaintInfo& paintInfo, const IntRect& rect)
933 {
934     return paintMediaButton(renderObject, paintInfo.context, rect, GTK_STOCK_MEDIA_REWIND);
935 }
936
937 bool RenderThemeGtk::paintMediaSeekForwardButton(RenderObject* renderObject, const PaintInfo& paintInfo, const IntRect& rect)
938 {
939     return paintMediaButton(renderObject, paintInfo.context, rect, GTK_STOCK_MEDIA_FORWARD);
940 }
941
942 bool RenderThemeGtk::paintMediaSliderTrack(RenderObject* o, const PaintInfo& paintInfo, const IntRect& r)
943 {
944     GraphicsContext* context = paintInfo.context;
945
946     context->fillRect(FloatRect(r), m_panelColor, ColorSpaceDeviceRGB);
947     context->fillRect(FloatRect(IntRect(r.x(), r.y() + (r.height() - m_mediaSliderHeight) / 2,
948                                         r.width(), m_mediaSliderHeight)), m_sliderColor, ColorSpaceDeviceRGB);
949
950     RenderStyle* style = o->style();
951     HTMLMediaElement* mediaElement = toParentMediaElement(o);
952
953     if (!mediaElement)
954         return false;
955
956     // Draw the buffered ranges. This code is highly inspired from
957     // Chrome for the gradient code.
958     float mediaDuration = mediaElement->duration();
959     RefPtr<TimeRanges> timeRanges = mediaElement->buffered();
960     IntRect trackRect = r;
961     int totalWidth = trackRect.width();
962
963     trackRect.inflate(-style->borderLeftWidth());
964     context->save();
965     context->setStrokeStyle(NoStroke);
966
967     for (unsigned index = 0; index < timeRanges->length(); ++index) {
968         ExceptionCode ignoredException;
969         float start = timeRanges->start(index, ignoredException);
970         float end = timeRanges->end(index, ignoredException);
971         int width = ((end - start) * totalWidth) / mediaDuration;
972         IntRect rangeRect;
973         if (!index) {
974             rangeRect = trackRect;
975             rangeRect.setWidth(width);
976         } else {
977             rangeRect.setLocation(IntPoint(trackRect.x() + start / mediaDuration* totalWidth, trackRect.y()));
978             rangeRect.setSize(IntSize(width, trackRect.height()));
979         }
980
981         // Don't bother drawing empty range.
982         if (rangeRect.isEmpty())
983             continue;
984
985         IntPoint sliderTopLeft = rangeRect.location();
986         IntPoint sliderTopRight = sliderTopLeft;
987         sliderTopRight.move(0, rangeRect.height());
988
989         RefPtr<Gradient> gradient = Gradient::create(sliderTopLeft, sliderTopRight);
990         Color startColor = m_panelColor;
991         gradient->addColorStop(0.0, startColor);
992         gradient->addColorStop(1.0, Color(startColor.red() / 2, startColor.green() / 2, startColor.blue() / 2, startColor.alpha()));
993
994         context->setFillGradient(gradient);
995         context->fillRect(rangeRect);
996     }
997
998     context->restore();
999     return false;
1000 }
1001
1002 bool RenderThemeGtk::paintMediaSliderThumb(RenderObject* o, const PaintInfo& paintInfo, const IntRect& r)
1003 {
1004     // Make the thumb nicer with rounded corners.
1005     paintInfo.context->fillRoundedRect(r, IntSize(3, 3), IntSize(3, 3), IntSize(3, 3), IntSize(3, 3), m_sliderThumbColor, ColorSpaceDeviceRGB);
1006     return false;
1007 }
1008 #endif
1009
1010 #if ENABLE(PROGRESS_TAG)
1011 double RenderThemeGtk::animationRepeatIntervalForProgressBar(RenderProgress*) const
1012 {
1013     // FIXME: It doesn't look like there is a good way yet to support animated
1014     // progress bars with the Mozilla theme drawing code.
1015     return 0;
1016 }
1017
1018 double RenderThemeGtk::animationDurationForProgressBar(RenderProgress*) const
1019 {
1020     // FIXME: It doesn't look like there is a good way yet to support animated
1021     // progress bars with the Mozilla theme drawing code.
1022     return 0;
1023 }
1024
1025 void RenderThemeGtk::adjustProgressBarStyle(CSSStyleSelector*, RenderStyle* style, Element*) const
1026 {
1027     style->setBoxShadow(0);
1028 }
1029
1030 bool RenderThemeGtk::paintProgressBar(RenderObject* renderObject, const PaintInfo& paintInfo, const IntRect& rect)
1031 {
1032     if (!renderObject->isProgress())
1033         return true;
1034
1035     GtkWidget* progressBarWidget = moz_gtk_get_progress_widget();
1036     if (!progressBarWidget)
1037         return true;
1038
1039     if (paintRenderObject(MOZ_GTK_PROGRESSBAR, renderObject, paintInfo.context, rect))
1040         return true;
1041
1042     IntRect chunkRect(rect);
1043     RenderProgress* renderProgress = toRenderProgress(renderObject);
1044
1045     GtkStyle* style = gtk_widget_get_style(progressBarWidget);
1046     chunkRect.setHeight(chunkRect.height() - (2 * style->ythickness));
1047     chunkRect.setY(chunkRect.y() + style->ythickness);
1048     chunkRect.setWidth((chunkRect.width() - (2 * style->xthickness)) * renderProgress->position());
1049     if (renderObject->style()->direction() == RTL)
1050         chunkRect.setX(rect.x() + rect.width() - chunkRect.width() - style->xthickness);
1051     else
1052         chunkRect.setX(chunkRect.x() + style->xthickness);
1053
1054     return paintRenderObject(MOZ_GTK_PROGRESS_CHUNK, renderObject, paintInfo.context, chunkRect);
1055 }
1056 #endif
1057
1058 }