RenderThemeGtk.cpp revision 65f03d4f644ce73618e5f4f50dd694b26f55ae12
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 "CSSValueKeywords.h"
29#include "GOwnPtr.h"
30#include "Gradient.h"
31#include "GraphicsContext.h"
32#include "GtkVersioning.h"
33#include "HTMLMediaElement.h"
34#include "HTMLNames.h"
35#include "MediaControlElements.h"
36#include "PaintInfo.h"
37#include "RenderBox.h"
38#include "RenderObject.h"
39#include "TimeRanges.h"
40#include "UserAgentStyleSheets.h"
41#include <gdk/gdk.h>
42#include <gtk/gtk.h>
43
44namespace WebCore {
45
46using namespace HTMLNames;
47
48#if ENABLE(VIDEO)
49static HTMLMediaElement* getMediaElementFromRenderObject(RenderObject* o)
50{
51    Node* node = o->node();
52    Node* mediaNode = node ? node->shadowAncestorNode() : 0;
53    if (!mediaNode || (!mediaNode->hasTagName(videoTag) && !mediaNode->hasTagName(audioTag)))
54        return 0;
55
56    return static_cast<HTMLMediaElement*>(mediaNode);
57}
58
59static GtkIconSize getMediaButtonIconSize(int mediaIconSize)
60{
61    GtkIconSize iconSize = gtk_icon_size_from_name("webkit-media-button-size");
62    if (!iconSize)
63        iconSize = gtk_icon_size_register("webkit-media-button-size", mediaIconSize, mediaIconSize);
64    return iconSize;
65}
66
67void RenderThemeGtk::initMediaButtons()
68{
69    static bool iconsInitialized = false;
70
71    if (iconsInitialized)
72        return;
73
74    GRefPtr<GtkIconFactory> iconFactory = adoptGRef(gtk_icon_factory_new());
75    GtkIconSource* iconSource = gtk_icon_source_new();
76    const char* icons[] = { "audio-volume-high", "audio-volume-muted" };
77
78    gtk_icon_factory_add_default(iconFactory.get());
79
80    for (size_t i = 0; i < G_N_ELEMENTS(icons); ++i) {
81        gtk_icon_source_set_icon_name(iconSource, icons[i]);
82        GtkIconSet* iconSet = gtk_icon_set_new();
83        gtk_icon_set_add_source(iconSet, iconSource);
84        gtk_icon_factory_add(iconFactory.get(), icons[i], iconSet);
85        gtk_icon_set_unref(iconSet);
86    }
87
88    gtk_icon_source_free(iconSource);
89
90    iconsInitialized = true;
91}
92#endif
93
94PassRefPtr<RenderTheme> RenderThemeGtk::create()
95{
96    return adoptRef(new RenderThemeGtk());
97}
98
99PassRefPtr<RenderTheme> RenderTheme::themeForPage(Page* page)
100{
101    static RenderTheme* rt = RenderThemeGtk::create().releaseRef();
102    return rt;
103}
104
105RenderThemeGtk::RenderThemeGtk()
106    : m_panelColor(Color::white)
107    , m_sliderColor(Color::white)
108    , m_sliderThumbColor(Color::white)
109    , m_mediaIconSize(16)
110    , m_mediaSliderHeight(14)
111    , m_mediaSliderThumbWidth(12)
112    , m_mediaSliderThumbHeight(12)
113{
114    platformInit();
115#if ENABLE(VIDEO)
116    initMediaColors();
117    initMediaButtons();
118#endif
119}
120
121static bool supportsFocus(ControlPart appearance)
122{
123    switch (appearance) {
124    case PushButtonPart:
125    case ButtonPart:
126    case TextFieldPart:
127    case TextAreaPart:
128    case SearchFieldPart:
129    case MenulistPart:
130    case RadioPart:
131    case CheckboxPart:
132    case SliderHorizontalPart:
133    case SliderVerticalPart:
134        return true;
135    default:
136        return false;
137    }
138}
139
140bool RenderThemeGtk::supportsFocusRing(const RenderStyle* style) const
141{
142    return supportsFocus(style->appearance());
143}
144
145bool RenderThemeGtk::controlSupportsTints(const RenderObject* o) const
146{
147    return isEnabled(o);
148}
149
150int RenderThemeGtk::baselinePosition(const RenderObject* o) const
151{
152    if (!o->isBox())
153        return 0;
154
155    // FIXME: This strategy is possibly incorrect for the GTK+ port.
156    if (o->style()->appearance() == CheckboxPart
157        || o->style()->appearance() == RadioPart) {
158        const RenderBox* box = toRenderBox(o);
159        return box->marginTop() + box->height() - 2;
160    }
161
162    return RenderTheme::baselinePosition(o);
163}
164
165// This is used in RenderThemeGtk2 and RenderThemeGtk3. Normally, it would be in
166// the RenderThemeGtk header (perhaps as a static method), but we want to avoid
167// having to include GTK+ headers only for the GtkTextDirection enum.
168GtkTextDirection gtkTextDirection(TextDirection direction)
169{
170    switch (direction) {
171    case RTL:
172        return GTK_TEXT_DIR_RTL;
173    case LTR:
174        return GTK_TEXT_DIR_LTR;
175    default:
176        return GTK_TEXT_DIR_NONE;
177    }
178}
179
180static GtkStateType gtkIconState(RenderTheme* theme, RenderObject* renderObject)
181{
182    if (!theme->isEnabled(renderObject))
183        return GTK_STATE_INSENSITIVE;
184    if (theme->isPressed(renderObject))
185        return GTK_STATE_ACTIVE;
186    if (theme->isHovered(renderObject))
187        return GTK_STATE_PRELIGHT;
188
189    return GTK_STATE_NORMAL;
190}
191
192void RenderThemeGtk::adjustButtonStyle(CSSStyleSelector* selector, RenderStyle* style, WebCore::Element* e) const
193{
194    // Some layout tests check explicitly that buttons ignore line-height.
195    if (style->appearance() == PushButtonPart)
196        style->setLineHeight(RenderStyle::initialLineHeight());
197}
198
199void RenderThemeGtk::adjustMenuListStyle(CSSStyleSelector* selector, RenderStyle* style, Element* e) const
200{
201    // The tests check explicitly that select menu buttons ignore line height.
202    style->setLineHeight(RenderStyle::initialLineHeight());
203
204    // We cannot give a proper rendering when border radius is active, unfortunately.
205    style->resetBorderRadius();
206}
207
208void RenderThemeGtk::adjustMenuListButtonStyle(CSSStyleSelector* selector, RenderStyle* style, Element* e) const
209{
210    adjustMenuListStyle(selector, style, e);
211}
212
213bool RenderThemeGtk::paintMenuListButton(RenderObject* object, const PaintInfo& info, const IntRect& rect)
214{
215    return paintMenuList(object, info, rect);
216}
217
218bool RenderThemeGtk::paintTextArea(RenderObject* o, const PaintInfo& i, const IntRect& r)
219{
220    return paintTextField(o, i, r);
221}
222
223static void paintGdkPixbuf(GraphicsContext* context, const GdkPixbuf* icon, const IntPoint& iconPoint)
224{
225    cairo_t* cr = context->platformContext();
226    cairo_save(cr);
227    gdk_cairo_set_source_pixbuf(cr, icon, iconPoint.x(), iconPoint.y());
228    cairo_paint(cr);
229    cairo_restore(cr);
230}
231
232void RenderThemeGtk::adjustSearchFieldResultsButtonStyle(CSSStyleSelector* selector, RenderStyle* style, Element* e) const
233{
234    adjustSearchFieldCancelButtonStyle(selector, style, e);
235}
236
237bool RenderThemeGtk::paintSearchFieldResultsButton(RenderObject* o, const PaintInfo& i, const IntRect& rect)
238{
239    return paintSearchFieldResultsDecoration(o, i, rect);
240}
241
242void RenderThemeGtk::adjustSearchFieldResultsDecorationStyle(CSSStyleSelector* selector, RenderStyle* style, Element* e) const
243{
244    style->resetBorder();
245    style->resetPadding();
246
247    gint width = 0, height = 0;
248    gtk_icon_size_lookup(GTK_ICON_SIZE_MENU, &width, &height);
249    style->setWidth(Length(width, Fixed));
250    style->setHeight(Length(height, Fixed));
251}
252
253static IntPoint centerRectVerticallyInParentInputElement(RenderObject* object, const IntRect& rect)
254{
255    Node* input = object->node()->shadowAncestorNode(); // Get the renderer of <input> element.
256    if (!input->renderer()->isBox())
257        return rect.topLeft();
258
259    // If possible center the y-coordinate of the rect vertically in the parent input element.
260    // We also add one pixel here to ensure that the y coordinate is rounded up for box heights
261    // that are even, which looks in relation to the box text.
262    IntRect inputContentBox = toRenderBox(input->renderer())->absoluteContentBox();
263
264    return IntPoint(rect.x(), inputContentBox.y() + (inputContentBox.height() - rect.height() + 1) / 2);
265}
266
267bool RenderThemeGtk::paintSearchFieldResultsDecoration(RenderObject* renderObject, const PaintInfo& paintInfo, const IntRect& rect)
268{
269    GRefPtr<GdkPixbuf> icon = getStockIcon(GTK_TYPE_ENTRY, GTK_STOCK_FIND,
270                                           gtkTextDirection(renderObject->style()->direction()),
271                                           gtkIconState(this, renderObject), GTK_ICON_SIZE_MENU);
272    paintGdkPixbuf(paintInfo.context, icon.get(), centerRectVerticallyInParentInputElement(renderObject, rect));
273    return false;
274}
275
276void RenderThemeGtk::adjustSearchFieldCancelButtonStyle(CSSStyleSelector* selector, RenderStyle* style, Element* e) const
277{
278    style->resetBorder();
279    style->resetPadding();
280
281    gint width = 0, height = 0;
282    gtk_icon_size_lookup(GTK_ICON_SIZE_MENU, &width, &height);
283    style->setWidth(Length(width, Fixed));
284    style->setHeight(Length(height, Fixed));
285}
286
287bool RenderThemeGtk::paintSearchFieldCancelButton(RenderObject* renderObject, const PaintInfo& paintInfo, const IntRect& rect)
288{
289    GRefPtr<GdkPixbuf> icon = getStockIcon(GTK_TYPE_ENTRY, GTK_STOCK_CLEAR,
290                                           gtkTextDirection(renderObject->style()->direction()),
291                                           gtkIconState(this, renderObject), GTK_ICON_SIZE_MENU);
292    paintGdkPixbuf(paintInfo.context, icon.get(), centerRectVerticallyInParentInputElement(renderObject, rect));
293    return false;
294}
295
296void RenderThemeGtk::adjustSearchFieldStyle(CSSStyleSelector* selector, RenderStyle* style, Element* e) const
297{
298    // We cannot give a proper rendering when border radius is active, unfortunately.
299    style->resetBorderRadius();
300    style->setLineHeight(RenderStyle::initialLineHeight());
301}
302
303bool RenderThemeGtk::paintSearchField(RenderObject* o, const PaintInfo& i, const IntRect& rect)
304{
305    return paintTextField(o, i, rect);
306}
307
308void RenderThemeGtk::adjustSliderTrackStyle(CSSStyleSelector*, RenderStyle* style, Element*) const
309{
310    style->setBoxShadow(0);
311}
312
313void RenderThemeGtk::adjustSliderThumbStyle(CSSStyleSelector*, RenderStyle* style, Element*) const
314{
315    style->setBoxShadow(0);
316}
317
318double RenderThemeGtk::caretBlinkInterval() const
319{
320    GtkSettings* settings = gtk_settings_get_default();
321
322    gboolean shouldBlink;
323    gint time;
324
325    g_object_get(settings, "gtk-cursor-blink", &shouldBlink, "gtk-cursor-blink-time", &time, NULL);
326
327    if (!shouldBlink)
328        return 0;
329
330    return time / 2000.;
331}
332
333static double getScreenDPI()
334{
335    // FIXME: Really this should be the widget's screen.
336    GdkScreen* screen = gdk_screen_get_default();
337    if (!screen)
338        return 96; // Default to 96 DPI.
339
340    float dpi = gdk_screen_get_resolution(screen);
341    if (dpi <= 0)
342        return 96;
343    return dpi;
344}
345
346void RenderThemeGtk::systemFont(int, FontDescription& fontDescription) const
347{
348    GtkSettings* settings = gtk_settings_get_default();
349    if (!settings)
350        return;
351
352    // This will be a font selection string like "Sans 10" so we cannot use it as the family name.
353    GOwnPtr<gchar> fontName;
354    g_object_get(settings, "gtk-font-name", &fontName.outPtr(), NULL);
355
356    PangoFontDescription* pangoDescription = pango_font_description_from_string(fontName.get());
357    if (!pangoDescription)
358        return;
359
360    fontDescription.firstFamily().setFamily(pango_font_description_get_family(pangoDescription));
361
362    int size = pango_font_description_get_size(pangoDescription) / PANGO_SCALE;
363    // If the size of the font is in points, we need to convert it to pixels.
364    if (!pango_font_description_get_size_is_absolute(pangoDescription))
365        size = size * (getScreenDPI() / 72.0);
366
367    fontDescription.setSpecifiedSize(size);
368    fontDescription.setIsAbsoluteSize(true);
369    fontDescription.setGenericFamily(FontDescription::NoFamily);
370    fontDescription.setWeight(FontWeightNormal);
371    fontDescription.setItalic(false);
372    pango_font_description_free(pangoDescription);
373}
374
375void RenderThemeGtk::platformColorsDidChange()
376{
377#if ENABLE(VIDEO)
378    initMediaColors();
379#endif
380    RenderTheme::platformColorsDidChange();
381}
382
383#if ENABLE(VIDEO)
384String RenderThemeGtk::extraMediaControlsStyleSheet()
385{
386    return String(mediaControlsGtkUserAgentStyleSheet, sizeof(mediaControlsGtkUserAgentStyleSheet));
387}
388
389void RenderThemeGtk::adjustMediaSliderThumbSize(RenderObject* renderObject) const
390{
391    ASSERT(renderObject->style()->appearance() == MediaSliderThumbPart);
392    renderObject->style()->setWidth(Length(m_mediaSliderThumbWidth, Fixed));
393    renderObject->style()->setHeight(Length(m_mediaSliderThumbHeight, Fixed));
394}
395
396bool RenderThemeGtk::paintMediaButton(RenderObject* renderObject, GraphicsContext* context, const IntRect& rect, const char* iconName)
397{
398    GRefPtr<GdkPixbuf> icon = getStockIcon(GTK_TYPE_CONTAINER, iconName,
399                                           gtkTextDirection(renderObject->style()->direction()),
400                                           gtkIconState(this, renderObject),
401                                           getMediaButtonIconSize(m_mediaIconSize));
402    IntPoint iconPoint(rect.x() + (rect.width() - m_mediaIconSize) / 2,
403                       rect.y() + (rect.height() - m_mediaIconSize) / 2);
404    context->fillRect(FloatRect(rect), m_panelColor, ColorSpaceDeviceRGB);
405    paintGdkPixbuf(context, icon.get(), iconPoint);
406    return false;
407}
408
409bool RenderThemeGtk::paintMediaFullscreenButton(RenderObject* renderObject, const PaintInfo& paintInfo, const IntRect& rect)
410{
411    return paintMediaButton(renderObject, paintInfo.context, rect, GTK_STOCK_FULLSCREEN);
412}
413
414bool RenderThemeGtk::paintMediaMuteButton(RenderObject* renderObject, const PaintInfo& paintInfo, const IntRect& rect)
415{
416    HTMLMediaElement* mediaElement = getMediaElementFromRenderObject(renderObject);
417    if (!mediaElement)
418        return false;
419
420    return paintMediaButton(renderObject, paintInfo.context, rect, mediaElement->muted() ? "audio-volume-muted" : "audio-volume-high");
421}
422
423bool RenderThemeGtk::paintMediaPlayButton(RenderObject* renderObject, const PaintInfo& paintInfo, const IntRect& rect)
424{
425    Node* node = renderObject->node();
426    if (!node)
427        return false;
428
429    MediaControlPlayButtonElement* button = static_cast<MediaControlPlayButtonElement*>(node);
430    return paintMediaButton(renderObject, paintInfo.context, rect, button->displayType() == MediaPlayButton ? GTK_STOCK_MEDIA_PLAY : GTK_STOCK_MEDIA_PAUSE);
431}
432
433bool RenderThemeGtk::paintMediaSeekBackButton(RenderObject* renderObject, const PaintInfo& paintInfo, const IntRect& rect)
434{
435    return paintMediaButton(renderObject, paintInfo.context, rect, GTK_STOCK_MEDIA_REWIND);
436}
437
438bool RenderThemeGtk::paintMediaSeekForwardButton(RenderObject* renderObject, const PaintInfo& paintInfo, const IntRect& rect)
439{
440    return paintMediaButton(renderObject, paintInfo.context, rect, GTK_STOCK_MEDIA_FORWARD);
441}
442
443bool RenderThemeGtk::paintMediaSliderTrack(RenderObject* o, const PaintInfo& paintInfo, const IntRect& r)
444{
445    GraphicsContext* context = paintInfo.context;
446
447    context->fillRect(FloatRect(r), m_panelColor, ColorSpaceDeviceRGB);
448    context->fillRect(FloatRect(IntRect(r.x(), r.y() + (r.height() - m_mediaSliderHeight) / 2,
449                                        r.width(), m_mediaSliderHeight)), m_sliderColor, ColorSpaceDeviceRGB);
450
451    RenderStyle* style = o->style();
452    HTMLMediaElement* mediaElement = toParentMediaElement(o);
453
454    if (!mediaElement)
455        return false;
456
457    // Draw the buffered ranges. This code is highly inspired from
458    // Chrome for the gradient code.
459    float mediaDuration = mediaElement->duration();
460    RefPtr<TimeRanges> timeRanges = mediaElement->buffered();
461    IntRect trackRect = r;
462    int totalWidth = trackRect.width();
463
464    trackRect.inflate(-style->borderLeftWidth());
465    context->save();
466    context->setStrokeStyle(NoStroke);
467
468    for (unsigned index = 0; index < timeRanges->length(); ++index) {
469        ExceptionCode ignoredException;
470        float start = timeRanges->start(index, ignoredException);
471        float end = timeRanges->end(index, ignoredException);
472        int width = ((end - start) * totalWidth) / mediaDuration;
473        IntRect rangeRect;
474        if (!index) {
475            rangeRect = trackRect;
476            rangeRect.setWidth(width);
477        } else {
478            rangeRect.setLocation(IntPoint(trackRect.x() + start / mediaDuration* totalWidth, trackRect.y()));
479            rangeRect.setSize(IntSize(width, trackRect.height()));
480        }
481
482        // Don't bother drawing empty range.
483        if (rangeRect.isEmpty())
484            continue;
485
486        IntPoint sliderTopLeft = rangeRect.location();
487        IntPoint sliderTopRight = sliderTopLeft;
488        sliderTopRight.move(0, rangeRect.height());
489
490        RefPtr<Gradient> gradient = Gradient::create(sliderTopLeft, sliderTopRight);
491        Color startColor = m_panelColor;
492        gradient->addColorStop(0.0, startColor);
493        gradient->addColorStop(1.0, Color(startColor.red() / 2, startColor.green() / 2, startColor.blue() / 2, startColor.alpha()));
494
495        context->setFillGradient(gradient);
496        context->fillRect(rangeRect);
497    }
498
499    context->restore();
500    return false;
501}
502
503bool RenderThemeGtk::paintMediaSliderThumb(RenderObject* o, const PaintInfo& paintInfo, const IntRect& r)
504{
505    // Make the thumb nicer with rounded corners.
506    paintInfo.context->fillRoundedRect(r, IntSize(3, 3), IntSize(3, 3), IntSize(3, 3), IntSize(3, 3), m_sliderThumbColor, ColorSpaceDeviceRGB);
507    return false;
508}
509
510bool RenderThemeGtk::paintMediaVolumeSliderContainer(RenderObject*, const PaintInfo& paintInfo, const IntRect& rect)
511{
512    GraphicsContext* context = paintInfo.context;
513    context->fillRect(FloatRect(rect), m_panelColor, ColorSpaceDeviceRGB);
514    return false;
515}
516
517bool RenderThemeGtk::paintMediaVolumeSliderTrack(RenderObject* renderObject, const PaintInfo& paintInfo, const IntRect& rect)
518{
519    return paintSliderTrack(renderObject, paintInfo, rect);
520}
521
522bool RenderThemeGtk::paintMediaVolumeSliderThumb(RenderObject* renderObject, const PaintInfo& paintInfo, const IntRect& rect)
523{
524    return paintSliderThumb(renderObject, paintInfo, rect);
525}
526
527String RenderThemeGtk::formatMediaControlsCurrentTime(float currentTime, float duration) const
528{
529    return formatMediaControlsTime(currentTime) + " / " + formatMediaControlsTime(duration);
530}
531
532bool RenderThemeGtk::paintMediaCurrentTime(RenderObject* renderObject, const PaintInfo& paintInfo, const IntRect& rect)
533{
534    GraphicsContext* context = paintInfo.context;
535
536    context->fillRect(FloatRect(rect), m_panelColor, ColorSpaceDeviceRGB);
537    return false;
538}
539#endif
540
541#if ENABLE(PROGRESS_TAG)
542void RenderThemeGtk::adjustProgressBarStyle(CSSStyleSelector*, RenderStyle* style, Element*) const
543{
544    style->setBoxShadow(0);
545}
546#endif
547
548}
549