2010-12-24 Carlos Garcia Campos <cgarcia@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     widgetState.depressed = false;
302
303     WidgetRenderingContext widgetContext(context, rect);
304     return !widgetContext.paintMozillaWidget(type, &widgetState, flags, gtkTextDirection(renderObject->style()->direction()));
305 }
306
307 static void setToggleSize(const RenderThemeGtk* theme, RenderStyle* style, ControlPart appearance)
308 {
309     // The width and height are both specified, so we shouldn't change them.
310     if (!style->width().isIntrinsicOrAuto() && !style->height().isAuto())
311         return;
312
313     // FIXME: This is probably not correct use of indicatorSize and indicatorSpacing.
314     gint indicatorSize, indicatorSpacing;
315     theme->getIndicatorMetrics(appearance, indicatorSize, indicatorSpacing);
316
317     // Other ports hard-code this to 13, but GTK+ users tend to demand the native look.
318     // It could be made a configuration option values other than 13 actually break site compatibility.
319     int length = indicatorSize + indicatorSpacing;
320     if (style->width().isIntrinsicOrAuto())
321         style->setWidth(Length(length, Fixed));
322
323     if (style->height().isAuto())
324         style->setHeight(Length(length, Fixed));
325 }
326
327 void RenderThemeGtk::setCheckboxSize(RenderStyle* style) const
328 {
329     setToggleSize(this, style, RadioPart);
330 }
331
332 bool RenderThemeGtk::paintCheckbox(RenderObject* o, const PaintInfo& i, const IntRect& rect)
333 {
334     return paintRenderObject(MOZ_GTK_CHECKBUTTON, o, i.context, rect, isChecked(o));
335 }
336
337 void RenderThemeGtk::setRadioSize(RenderStyle* style) const
338 {
339     setToggleSize(this, style, RadioPart);
340 }
341
342 bool RenderThemeGtk::paintRadio(RenderObject* o, const PaintInfo& i, const IntRect& rect)
343 {
344     return paintRenderObject(MOZ_GTK_RADIOBUTTON, o, i.context, rect, isChecked(o));
345 }
346
347 void RenderThemeGtk::adjustButtonStyle(CSSStyleSelector* selector, RenderStyle* style, WebCore::Element* e) const
348 {
349     // Some layout tests check explicitly that buttons ignore line-height.
350     if (style->appearance() == PushButtonPart)
351         style->setLineHeight(RenderStyle::initialLineHeight());
352 }
353
354 bool RenderThemeGtk::paintButton(RenderObject* object, const PaintInfo& info, const IntRect& rect)
355 {
356     if (info.context->paintingDisabled())
357         return false;
358
359     GtkWidget* widget = gtkButton();
360     IntRect buttonRect(IntPoint(), rect.size());
361     IntRect focusRect(buttonRect);
362
363     GtkStateType state = getGtkStateType(object);
364     gtk_widget_set_state(widget, state);
365     gtk_widget_set_direction(widget, gtkTextDirection(object->style()->direction()));
366
367     if (isFocused(object)) {
368         if (isEnabled(object)) {
369 #if !GTK_CHECK_VERSION(2, 22, 0)
370             GTK_WIDGET_SET_FLAGS(widget, GTK_HAS_FOCUS);
371 #endif
372             g_object_set(widget, "has-focus", TRUE, NULL);
373         }
374
375         gboolean interiorFocus = 0, focusWidth = 0, focusPadding = 0;
376         gtk_widget_style_get(widget,
377                              "interior-focus", &interiorFocus,
378                              "focus-line-width", &focusWidth,
379                              "focus-padding", &focusPadding, NULL);
380         // If we are using exterior focus, we shrink the button rect down before
381         // drawing. If we are using interior focus we shrink the focus rect. This
382         // approach originates from the Mozilla theme drawing code (gtk2drawing.c).
383         if (interiorFocus) {
384             GtkStyle* style = gtk_widget_get_style(widget);
385             focusRect.inflateX(-style->xthickness - focusPadding);
386             focusRect.inflateY(-style->ythickness - focusPadding);
387         } else {
388             buttonRect.inflateX(-focusWidth - focusPadding);
389             buttonRect.inflateY(-focusPadding - focusPadding);
390         }
391     }
392
393     WidgetRenderingContext widgetContext(info.context, rect);
394     GtkShadowType shadowType = state == GTK_STATE_ACTIVE ? GTK_SHADOW_IN : GTK_SHADOW_OUT;
395     widgetContext.gtkPaintBox(buttonRect, widget, state, shadowType, "button");
396     if (isFocused(object))
397         widgetContext.gtkPaintFocus(focusRect, widget, state, "button");
398
399 #if !GTK_CHECK_VERSION(2, 22, 0)
400     GTK_WIDGET_UNSET_FLAGS(widget, GTK_HAS_FOCUS);
401 #endif
402     g_object_set(widget, "has-focus", FALSE, NULL);
403     return false;
404 }
405
406 static void getComboBoxPadding(RenderStyle* style, int& left, int& top, int& right, int& bottom)
407 {
408     // If this menu list button isn't drawn using the native theme, we
409     // don't add any extra padding beyond what WebCore already uses.
410     if (style->appearance() == NoControlPart)
411         return;
412     moz_gtk_get_widget_border(MOZ_GTK_DROPDOWN, &left, &top, &right, &bottom,
413                               gtkTextDirection(style->direction()), TRUE);
414 }
415
416 int RenderThemeGtk::popupInternalPaddingLeft(RenderStyle* style) const
417 {
418     int left = 0, top = 0, right = 0, bottom = 0;
419     getComboBoxPadding(style, left, top, right, bottom);
420     return left;
421 }
422
423 int RenderThemeGtk::popupInternalPaddingRight(RenderStyle* style) const
424 {
425     int left = 0, top = 0, right = 0, bottom = 0;
426     getComboBoxPadding(style, left, top, right, bottom);
427     return right;
428 }
429
430 int RenderThemeGtk::popupInternalPaddingTop(RenderStyle* style) const
431 {
432     int left = 0, top = 0, right = 0, bottom = 0;
433     getComboBoxPadding(style, left, top, right, bottom);
434     return top;
435 }
436
437 int RenderThemeGtk::popupInternalPaddingBottom(RenderStyle* style) const
438 {
439     int left = 0, top = 0, right = 0, bottom = 0;
440     getComboBoxPadding(style, left, top, right, bottom);
441     return bottom;
442 }
443
444 void RenderThemeGtk::adjustMenuListStyle(CSSStyleSelector* selector, RenderStyle* style, Element* e) const
445 {
446     // The tests check explicitly that select menu buttons ignore line height.
447     style->setLineHeight(RenderStyle::initialLineHeight());
448
449     // We cannot give a proper rendering when border radius is active, unfortunately.
450     style->resetBorderRadius();
451 }
452
453 void RenderThemeGtk::adjustMenuListButtonStyle(CSSStyleSelector* selector, RenderStyle* style, Element* e) const
454 {
455     adjustMenuListStyle(selector, style, e);
456 }
457
458 bool RenderThemeGtk::paintMenuList(RenderObject* o, const PaintInfo& i, const IntRect& rect)
459 {
460     return paintRenderObject(MOZ_GTK_DROPDOWN, o, i.context, rect);
461 }
462
463 bool RenderThemeGtk::paintMenuListButton(RenderObject* object, const PaintInfo& info, const IntRect& rect)
464 {
465     return paintMenuList(object, info, rect);
466 }
467
468 static void setTextInputBorders(RenderStyle* style)
469 {
470     // If this control isn't drawn using the native theme, we don't touch the borders.
471     if (style->appearance() == NoControlPart)
472         return;
473
474     // We cannot give a proper rendering when border radius is active, unfortunately.
475     style->resetBorderRadius();
476
477     int left = 0, top = 0, right = 0, bottom = 0;
478     moz_gtk_get_widget_border(MOZ_GTK_ENTRY, &left, &top, &right, &bottom,
479                               gtkTextDirection(style->direction()), TRUE);
480     style->setBorderLeftWidth(left);
481     style->setBorderTopWidth(top);
482     style->setBorderRightWidth(right);
483     style->setBorderBottomWidth(bottom);
484 }
485
486 void RenderThemeGtk::adjustTextFieldStyle(CSSStyleSelector* selector, RenderStyle* style, Element* e) const
487 {
488     setTextInputBorders(style);
489 }
490
491 bool RenderThemeGtk::paintTextField(RenderObject* o, const PaintInfo& i, const IntRect& rect)
492 {
493     return paintRenderObject(MOZ_GTK_ENTRY, o, i.context, rect);
494 }
495
496 void RenderThemeGtk::adjustTextAreaStyle(CSSStyleSelector* selector, RenderStyle* style, Element* e) const
497 {
498     setTextInputBorders(style);
499 }
500
501 bool RenderThemeGtk::paintTextArea(RenderObject* o, const PaintInfo& i, const IntRect& r)
502 {
503     return paintTextField(o, i, r);
504 }
505
506 void RenderThemeGtk::adjustSearchFieldResultsButtonStyle(CSSStyleSelector* selector, RenderStyle* style, Element* e) const
507 {
508     adjustSearchFieldCancelButtonStyle(selector, style, e);
509 }
510
511 bool RenderThemeGtk::paintSearchFieldResultsButton(RenderObject* o, const PaintInfo& i, const IntRect& rect)
512 {
513     return paintSearchFieldResultsDecoration(o, i, rect);
514 }
515
516 void RenderThemeGtk::adjustSearchFieldResultsDecorationStyle(CSSStyleSelector* selector, RenderStyle* style, Element* e) const
517 {
518     style->resetBorder();
519     style->resetPadding();
520
521     gint width = 0, height = 0;
522     gtk_icon_size_lookup(GTK_ICON_SIZE_MENU, &width, &height);
523     style->setWidth(Length(width, Fixed));
524     style->setHeight(Length(height, Fixed));
525 }
526
527 static IntPoint centerRectVerticallyInParentInputElement(RenderObject* object, const IntRect& rect)
528 {
529     Node* input = object->node()->shadowAncestorNode(); // Get the renderer of <input> element.
530     if (!input->renderer()->isBox())
531         return rect.topLeft();
532
533     // If possible center the y-coordinate of the rect vertically in the parent input element.
534     // We also add one pixel here to ensure that the y coordinate is rounded up for box heights
535     // that are even, which looks in relation to the box text.
536     IntRect inputContentBox = toRenderBox(input->renderer())->absoluteContentBox();
537
538     return IntPoint(rect.x(), inputContentBox.y() + (inputContentBox.height() - rect.height() + 1) / 2);
539 }
540
541 bool RenderThemeGtk::paintSearchFieldResultsDecoration(RenderObject* renderObject, const PaintInfo& paintInfo, const IntRect& rect)
542 {
543     GtkStyle* style = gtk_widget_get_style(GTK_WIDGET(gtkEntry()));
544     IntPoint iconPoint(centerRectVerticallyInParentInputElement(renderObject, rect));
545     paintStockIcon(paintInfo.context, iconPoint, style, GTK_STOCK_FIND,
546                    gtkTextDirection(renderObject->style()->direction()),
547                    gtkIconState(renderObject), GTK_ICON_SIZE_MENU);
548     return false;
549 }
550
551 void RenderThemeGtk::adjustSearchFieldCancelButtonStyle(CSSStyleSelector* selector, RenderStyle* style, Element* e) const
552 {
553     style->resetBorder();
554     style->resetPadding();
555
556     gint width = 0, height = 0;
557     gtk_icon_size_lookup(GTK_ICON_SIZE_MENU, &width, &height);
558     style->setWidth(Length(width, Fixed));
559     style->setHeight(Length(height, Fixed));
560 }
561
562 bool RenderThemeGtk::paintSearchFieldCancelButton(RenderObject* renderObject, const PaintInfo& paintInfo, const IntRect& rect)
563 {
564     GtkStyle* style = gtk_widget_get_style(GTK_WIDGET(gtkEntry()));
565     IntPoint iconPoint(centerRectVerticallyInParentInputElement(renderObject, rect));
566     paintStockIcon(paintInfo.context, iconPoint, style, GTK_STOCK_CLEAR,
567                    gtkTextDirection(renderObject->style()->direction()),
568                    gtkIconState(renderObject), GTK_ICON_SIZE_MENU);
569     return false;
570 }
571
572 void RenderThemeGtk::adjustSearchFieldStyle(CSSStyleSelector* selector, RenderStyle* style, Element* e) const
573 {
574     style->setLineHeight(RenderStyle::initialLineHeight());
575     setTextInputBorders(style);
576 }
577
578 bool RenderThemeGtk::paintSearchField(RenderObject* o, const PaintInfo& i, const IntRect& rect)
579 {
580     return paintTextField(o, i, rect);
581 }
582
583 bool RenderThemeGtk::paintSliderTrack(RenderObject* object, const PaintInfo& info, const IntRect& rect)
584 {
585     if (info.context->paintingDisabled())
586         return false;
587
588     ControlPart part = object->style()->appearance();
589     ASSERT(part == SliderHorizontalPart || part == SliderVerticalPart);
590
591     // We shrink the trough rect slightly to make room for the focus indicator.
592     IntRect troughRect(IntPoint(), rect.size()); // This is relative to rect.
593     GtkWidget* widget = 0;
594     if (part == SliderVerticalPart) {
595         widget = gtkVScale();
596         troughRect.inflateY(-gtk_widget_get_style(widget)->ythickness);
597     } else {
598         widget = gtkHScale();
599         troughRect.inflateX(-gtk_widget_get_style(widget)->xthickness);
600     }
601     gtk_widget_set_direction(widget, gtkTextDirection(object->style()->direction()));
602
603     WidgetRenderingContext widgetContext(info.context, rect);
604     widgetContext.gtkPaintBox(troughRect, widget, GTK_STATE_ACTIVE, GTK_SHADOW_OUT, "trough");
605     if (isFocused(object))
606         widgetContext.gtkPaintFocus(IntRect(IntPoint(), rect.size()), widget, getGtkStateType(object), "trough");
607
608     return false;
609 }
610
611 void RenderThemeGtk::adjustSliderTrackStyle(CSSStyleSelector*, RenderStyle* style, Element*) const
612 {
613     style->setBoxShadow(0);
614 }
615
616 bool RenderThemeGtk::paintSliderThumb(RenderObject* object, const PaintInfo& info, const IntRect& rect)
617 {
618     if (info.context->paintingDisabled())
619         return false;
620
621     ControlPart part = object->style()->appearance();
622     ASSERT(part == SliderThumbHorizontalPart || part == SliderThumbVerticalPart);
623
624     GtkWidget* widget = 0;
625     const char* detail = 0;
626     GtkOrientation orientation;
627     if (part == SliderThumbVerticalPart) {
628         widget = gtkVScale();
629         detail = "vscale";
630         orientation = GTK_ORIENTATION_VERTICAL;
631     } else {
632         widget = gtkHScale();
633         detail = "hscale";
634         orientation = GTK_ORIENTATION_HORIZONTAL;
635     }
636     gtk_widget_set_direction(widget, gtkTextDirection(object->style()->direction()));
637
638     // Only some themes have slider thumbs respond to clicks and some don't. This information is
639     // gathered via the 'activate-slider' property, but it's deprecated in GTK+ 2.22 and removed in
640     // GTK+ 3.x. The drawback of not honoring it is that slider thumbs change color when you click
641     // on them. 
642     IntRect thumbRect(IntPoint(), rect.size());
643     WidgetRenderingContext widgetContext(info.context, rect);
644     widgetContext.gtkPaintSlider(thumbRect, widget, getGtkStateType(object), GTK_SHADOW_OUT, detail, orientation);
645     return false;
646 }
647
648 void RenderThemeGtk::adjustSliderThumbStyle(CSSStyleSelector*, RenderStyle* style, Element*) const
649 {
650     style->setBoxShadow(0);
651 }
652
653 void RenderThemeGtk::adjustSliderThumbSize(RenderObject* o) const
654 {
655     ControlPart part = o->style()->appearance();
656 #if ENABLE(VIDEO)
657     if (part == MediaSliderThumbPart) {
658         o->style()->setWidth(Length(m_mediaSliderThumbWidth, Fixed));
659         o->style()->setHeight(Length(m_mediaSliderThumbHeight, Fixed));
660         return;
661     }
662     if (part == MediaVolumeSliderThumbPart)
663         return;
664 #endif
665
666     GtkWidget* widget = part == SliderThumbHorizontalPart ? gtkHScale() : gtkVScale();
667     int length = 0, width = 0;
668     gtk_widget_style_get(widget,
669                          "slider_length", &length,
670                          "slider_width", &width,
671                          NULL);
672
673     if (part == SliderThumbHorizontalPart) {
674         o->style()->setWidth(Length(length, Fixed));
675         o->style()->setHeight(Length(width, Fixed));
676         return;
677     }
678     ASSERT(part == SliderThumbVerticalPart);
679     o->style()->setWidth(Length(width, Fixed));
680     o->style()->setHeight(Length(length, Fixed));
681 }
682
683 Color RenderThemeGtk::platformActiveSelectionBackgroundColor() const
684 {
685     GtkWidget* widget = gtkEntry();
686     return gtk_widget_get_style(widget)->base[GTK_STATE_SELECTED];
687 }
688
689 Color RenderThemeGtk::platformInactiveSelectionBackgroundColor() const
690 {
691     GtkWidget* widget = gtkEntry();
692     return gtk_widget_get_style(widget)->base[GTK_STATE_ACTIVE];
693 }
694
695 Color RenderThemeGtk::platformActiveSelectionForegroundColor() const
696 {
697     GtkWidget* widget = gtkEntry();
698     return gtk_widget_get_style(widget)->text[GTK_STATE_SELECTED];
699 }
700
701 Color RenderThemeGtk::platformInactiveSelectionForegroundColor() const
702 {
703     GtkWidget* widget = gtkEntry();
704     return gtk_widget_get_style(widget)->text[GTK_STATE_ACTIVE];
705 }
706
707 Color RenderThemeGtk::activeListBoxSelectionBackgroundColor() const
708 {
709     GtkWidget* widget = gtkTreeView();
710     return gtk_widget_get_style(widget)->base[GTK_STATE_SELECTED];
711 }
712
713 Color RenderThemeGtk::inactiveListBoxSelectionBackgroundColor() const
714 {
715     GtkWidget* widget = gtkTreeView();
716     return gtk_widget_get_style(widget)->base[GTK_STATE_ACTIVE];
717 }
718
719 Color RenderThemeGtk::activeListBoxSelectionForegroundColor() const
720 {
721     GtkWidget* widget = gtkTreeView();
722     return gtk_widget_get_style(widget)->text[GTK_STATE_SELECTED];
723 }
724
725 Color RenderThemeGtk::inactiveListBoxSelectionForegroundColor() const
726 {
727     GtkWidget* widget = gtkTreeView();
728     return gtk_widget_get_style(widget)->text[GTK_STATE_ACTIVE];
729 }
730
731 double RenderThemeGtk::caretBlinkInterval() const
732 {
733     GtkSettings* settings = gtk_settings_get_default();
734
735     gboolean shouldBlink;
736     gint time;
737
738     g_object_get(settings, "gtk-cursor-blink", &shouldBlink, "gtk-cursor-blink-time", &time, NULL);
739
740     if (!shouldBlink)
741         return 0;
742
743     return time / 2000.;
744 }
745
746 static double getScreenDPI()
747 {
748     // FIXME: Really this should be the widget's screen.
749     GdkScreen* screen = gdk_screen_get_default();
750     if (!screen)
751         return 96; // Default to 96 DPI.
752
753     float dpi = gdk_screen_get_resolution(screen);
754     if (dpi <= 0)
755         return 96;
756     return dpi;
757 }
758
759 void RenderThemeGtk::systemFont(int, FontDescription& fontDescription) const
760 {
761     GtkSettings* settings = gtk_settings_get_default();
762     if (!settings)
763         return;
764
765     // This will be a font selection string like "Sans 10" so we cannot use it as the family name.
766     GOwnPtr<gchar> fontName;
767     g_object_get(settings, "gtk-font-name", &fontName.outPtr(), NULL);
768
769     PangoFontDescription* pangoDescription = pango_font_description_from_string(fontName.get());
770     if (!pangoDescription)
771         return;
772
773     fontDescription.firstFamily().setFamily(pango_font_description_get_family(pangoDescription));
774
775     int size = pango_font_description_get_size(pangoDescription) / PANGO_SCALE;
776     // If the size of the font is in points, we need to convert it to pixels.
777     if (!pango_font_description_get_size_is_absolute(pangoDescription))
778         size = size * (getScreenDPI() / 72.0);
779
780     fontDescription.setSpecifiedSize(size);
781     fontDescription.setIsAbsoluteSize(true);
782     fontDescription.setGenericFamily(FontDescription::NoFamily);
783     fontDescription.setWeight(FontWeightNormal);
784     fontDescription.setItalic(false);
785     pango_font_description_free(pangoDescription);
786 }
787
788 Color RenderThemeGtk::systemColor(int cssValueId) const
789 {
790     switch (cssValueId) {
791     case CSSValueButtontext:
792         return Color(gtk_widget_get_style(gtkButton())->fg[GTK_STATE_NORMAL]);
793     case CSSValueCaptiontext:
794         return Color(gtk_widget_get_style(gtkEntry())->fg[GTK_STATE_NORMAL]);
795     default:
796         return RenderTheme::systemColor(cssValueId);
797     }
798 }
799
800 static void gtkStyleSetCallback(GtkWidget* widget, GtkStyle* previous, RenderTheme* renderTheme)
801 {
802     // FIXME: Make sure this function doesn't get called many times for a single GTK+ style change signal.
803     renderTheme->platformColorsDidChange();
804 }
805
806 void RenderThemeGtk::setupWidgetAndAddToContainer(GtkWidget* widget, GtkWidget* window) const
807 {
808     gtk_container_add(GTK_CONTAINER(window), widget);
809     gtk_widget_realize(widget);
810     g_object_set_data(G_OBJECT(widget), "transparent-bg-hint", GINT_TO_POINTER(TRUE));
811
812     // FIXME: Perhaps this should only be called for the containing window or parent container.
813     g_signal_connect(widget, "style-set", G_CALLBACK(gtkStyleSetCallback), const_cast<RenderThemeGtk*>(this));
814 }
815
816 GtkWidget* RenderThemeGtk::gtkContainer() const
817 {
818     if (m_gtkContainer)
819         return m_gtkContainer;
820
821     m_gtkWindow = gtk_window_new(GTK_WINDOW_POPUP);
822 #ifdef GTK_API_VERSION_2
823     gtk_widget_set_colormap(m_gtkWindow, m_themeParts.colormap);
824 #endif
825     gtk_widget_realize(m_gtkWindow);
826     gtk_widget_set_name(m_gtkWindow, "MozillaGtkWidget");
827
828     m_gtkContainer = gtk_fixed_new();
829     setupWidgetAndAddToContainer(m_gtkContainer, m_gtkWindow);
830     return m_gtkContainer;
831 }
832
833 GtkWidget* RenderThemeGtk::gtkButton() const
834 {
835     if (m_gtkButton)
836         return m_gtkButton;
837     m_gtkButton = gtk_button_new();
838     setupWidgetAndAddToContainer(m_gtkButton, gtkContainer());
839     return m_gtkButton;
840 }
841
842 GtkWidget* RenderThemeGtk::gtkEntry() const
843 {
844     if (m_gtkEntry)
845         return m_gtkEntry;
846     m_gtkEntry = gtk_entry_new();
847     setupWidgetAndAddToContainer(m_gtkEntry, gtkContainer());
848     return m_gtkEntry;
849 }
850
851 GtkWidget* RenderThemeGtk::gtkTreeView() const
852 {
853     if (m_gtkTreeView)
854         return m_gtkTreeView;
855     m_gtkTreeView = gtk_tree_view_new();
856     setupWidgetAndAddToContainer(m_gtkTreeView, gtkContainer());
857     return m_gtkTreeView;
858 }
859
860 GtkWidget* RenderThemeGtk::gtkVScale() const
861 {
862     if (m_gtkVScale)
863         return m_gtkVScale;
864     m_gtkVScale = gtk_vscale_new(0);
865     setupWidgetAndAddToContainer(m_gtkVScale, gtkContainer());
866     return m_gtkVScale;
867 }
868
869 GtkWidget* RenderThemeGtk::gtkHScale() const
870 {
871     if (m_gtkHScale)
872         return m_gtkHScale;
873     m_gtkHScale = gtk_hscale_new(0);
874     setupWidgetAndAddToContainer(m_gtkHScale, gtkContainer());
875     return m_gtkHScale;
876 }
877
878 GtkWidget* RenderThemeGtk::gtkScrollbar()
879 {
880     return moz_gtk_get_scrollbar_widget();
881 }
882
883 void RenderThemeGtk::platformColorsDidChange()
884 {
885 #if ENABLE(VIDEO)
886     initMediaColors();
887 #endif
888     RenderTheme::platformColorsDidChange();
889 }
890
891 #if ENABLE(VIDEO)
892 String RenderThemeGtk::extraMediaControlsStyleSheet()
893 {
894     return String(mediaControlsGtkUserAgentStyleSheet, sizeof(mediaControlsGtkUserAgentStyleSheet));
895 }
896
897 bool RenderThemeGtk::paintMediaButton(RenderObject* renderObject, GraphicsContext* context, const IntRect& rect, const char* iconName)
898 {
899     GtkStyle* style = gtk_widget_get_style(GTK_WIDGET(gtkContainer()));
900     IntPoint iconPoint(rect.x() + (rect.width() - m_mediaIconSize) / 2,
901                        rect.y() + (rect.height() - m_mediaIconSize) / 2);
902     context->fillRect(FloatRect(rect), m_panelColor, ColorSpaceDeviceRGB);
903     paintStockIcon(context, iconPoint, style, iconName, gtkTextDirection(renderObject->style()->direction()),
904                    gtkIconState(renderObject), getMediaButtonIconSize(m_mediaIconSize));
905
906     return false;
907 }
908
909 bool RenderThemeGtk::paintMediaFullscreenButton(RenderObject* renderObject, const PaintInfo& paintInfo, const IntRect& rect)
910 {
911     return paintMediaButton(renderObject, paintInfo.context, rect, GTK_STOCK_FULLSCREEN);
912 }
913
914 bool RenderThemeGtk::paintMediaMuteButton(RenderObject* renderObject, const PaintInfo& paintInfo, const IntRect& rect)
915 {
916     HTMLMediaElement* mediaElement = getMediaElementFromRenderObject(renderObject);
917     if (!mediaElement)
918         return false;
919
920     return paintMediaButton(renderObject, paintInfo.context, rect, mediaElement->muted() ? "audio-volume-muted" : "audio-volume-high");
921 }
922
923 bool RenderThemeGtk::paintMediaPlayButton(RenderObject* renderObject, const PaintInfo& paintInfo, const IntRect& rect)
924 {
925     Node* node = renderObject->node();
926     if (!node)
927         return false;
928
929     MediaControlPlayButtonElement* button = static_cast<MediaControlPlayButtonElement*>(node);
930     return paintMediaButton(renderObject, paintInfo.context, rect, button->displayType() == MediaPlayButton ? GTK_STOCK_MEDIA_PLAY : GTK_STOCK_MEDIA_PAUSE);
931 }
932
933 bool RenderThemeGtk::paintMediaSeekBackButton(RenderObject* renderObject, const PaintInfo& paintInfo, const IntRect& rect)
934 {
935     return paintMediaButton(renderObject, paintInfo.context, rect, GTK_STOCK_MEDIA_REWIND);
936 }
937
938 bool RenderThemeGtk::paintMediaSeekForwardButton(RenderObject* renderObject, const PaintInfo& paintInfo, const IntRect& rect)
939 {
940     return paintMediaButton(renderObject, paintInfo.context, rect, GTK_STOCK_MEDIA_FORWARD);
941 }
942
943 bool RenderThemeGtk::paintMediaSliderTrack(RenderObject* o, const PaintInfo& paintInfo, const IntRect& r)
944 {
945     GraphicsContext* context = paintInfo.context;
946
947     context->fillRect(FloatRect(r), m_panelColor, ColorSpaceDeviceRGB);
948     context->fillRect(FloatRect(IntRect(r.x(), r.y() + (r.height() - m_mediaSliderHeight) / 2,
949                                         r.width(), m_mediaSliderHeight)), m_sliderColor, ColorSpaceDeviceRGB);
950
951     RenderStyle* style = o->style();
952     HTMLMediaElement* mediaElement = toParentMediaElement(o);
953
954     if (!mediaElement)
955         return false;
956
957     // Draw the buffered ranges. This code is highly inspired from
958     // Chrome for the gradient code.
959     float mediaDuration = mediaElement->duration();
960     RefPtr<TimeRanges> timeRanges = mediaElement->buffered();
961     IntRect trackRect = r;
962     int totalWidth = trackRect.width();
963
964     trackRect.inflate(-style->borderLeftWidth());
965     context->save();
966     context->setStrokeStyle(NoStroke);
967
968     for (unsigned index = 0; index < timeRanges->length(); ++index) {
969         ExceptionCode ignoredException;
970         float start = timeRanges->start(index, ignoredException);
971         float end = timeRanges->end(index, ignoredException);
972         int width = ((end - start) * totalWidth) / mediaDuration;
973         IntRect rangeRect;
974         if (!index) {
975             rangeRect = trackRect;
976             rangeRect.setWidth(width);
977         } else {
978             rangeRect.setLocation(IntPoint(trackRect.x() + start / mediaDuration* totalWidth, trackRect.y()));
979             rangeRect.setSize(IntSize(width, trackRect.height()));
980         }
981
982         // Don't bother drawing empty range.
983         if (rangeRect.isEmpty())
984             continue;
985
986         IntPoint sliderTopLeft = rangeRect.location();
987         IntPoint sliderTopRight = sliderTopLeft;
988         sliderTopRight.move(0, rangeRect.height());
989
990         RefPtr<Gradient> gradient = Gradient::create(sliderTopLeft, sliderTopRight);
991         Color startColor = m_panelColor;
992         gradient->addColorStop(0.0, startColor);
993         gradient->addColorStop(1.0, Color(startColor.red() / 2, startColor.green() / 2, startColor.blue() / 2, startColor.alpha()));
994
995         context->setFillGradient(gradient);
996         context->fillRect(rangeRect);
997     }
998
999     context->restore();
1000     return false;
1001 }
1002
1003 bool RenderThemeGtk::paintMediaSliderThumb(RenderObject* o, const PaintInfo& paintInfo, const IntRect& r)
1004 {
1005     // Make the thumb nicer with rounded corners.
1006     paintInfo.context->fillRoundedRect(r, IntSize(3, 3), IntSize(3, 3), IntSize(3, 3), IntSize(3, 3), m_sliderThumbColor, ColorSpaceDeviceRGB);
1007     return false;
1008 }
1009
1010 String RenderThemeGtk::formatMediaControlsCurrentTime(float currentTime, float duration) const
1011 {
1012     return formatMediaControlsTime(currentTime) + " / " + formatMediaControlsTime(duration);
1013 }
1014
1015 bool RenderThemeGtk::paintMediaCurrentTime(RenderObject* renderObject, const PaintInfo& paintInfo, const IntRect& rect)
1016 {
1017     GraphicsContext* context = paintInfo.context;
1018
1019     context->fillRect(FloatRect(rect), m_panelColor, ColorSpaceDeviceRGB);
1020     return false;
1021 }
1022 #endif
1023
1024 #if ENABLE(PROGRESS_TAG)
1025 double RenderThemeGtk::animationRepeatIntervalForProgressBar(RenderProgress*) const
1026 {
1027     // FIXME: It doesn't look like there is a good way yet to support animated
1028     // progress bars with the Mozilla theme drawing code.
1029     return 0;
1030 }
1031
1032 double RenderThemeGtk::animationDurationForProgressBar(RenderProgress*) const
1033 {
1034     // FIXME: It doesn't look like there is a good way yet to support animated
1035     // progress bars with the Mozilla theme drawing code.
1036     return 0;
1037 }
1038
1039 void RenderThemeGtk::adjustProgressBarStyle(CSSStyleSelector*, RenderStyle* style, Element*) const
1040 {
1041     style->setBoxShadow(0);
1042 }
1043
1044 bool RenderThemeGtk::paintProgressBar(RenderObject* renderObject, const PaintInfo& paintInfo, const IntRect& rect)
1045 {
1046     if (!renderObject->isProgress())
1047         return true;
1048
1049     GtkWidget* progressBarWidget = moz_gtk_get_progress_widget();
1050     if (!progressBarWidget)
1051         return true;
1052
1053     if (paintRenderObject(MOZ_GTK_PROGRESSBAR, renderObject, paintInfo.context, rect))
1054         return true;
1055
1056     IntRect chunkRect(rect);
1057     RenderProgress* renderProgress = toRenderProgress(renderObject);
1058
1059     GtkStyle* style = gtk_widget_get_style(progressBarWidget);
1060     chunkRect.setHeight(chunkRect.height() - (2 * style->ythickness));
1061     chunkRect.setY(chunkRect.y() + style->ythickness);
1062     chunkRect.setWidth((chunkRect.width() - (2 * style->xthickness)) * renderProgress->position());
1063     if (renderObject->style()->direction() == RTL)
1064         chunkRect.setX(rect.x() + rect.width() - chunkRect.width() - style->xthickness);
1065     else
1066         chunkRect.setX(chunkRect.x() + style->xthickness);
1067
1068     return paintRenderObject(MOZ_GTK_PROGRESS_CHUNK, renderObject, paintInfo.context, chunkRect);
1069 }
1070 #endif
1071
1072 }