2011-01-03 Martin Robinson <mrobinson@igalia.com>
[WebKit-https.git] / WebCore / platform / gtk / RenderThemeGtk3.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 #ifndef GTK_API_VERSION_2
29
30 #include "CSSValueKeywords.h"
31 #include "GraphicsContext.h"
32 #include "GtkVersioning.h"
33 #include "HTMLNames.h"
34 #include "MediaControlElements.h"
35 #include "Page.h"
36 #include "RenderObject.h"
37 #include "TextDirection.h"
38 #include "UserAgentStyleSheets.h"
39 #include "WidgetRenderingContext.h"
40 #include "gtkdrawing.h"
41 #include <gdk/gdk.h>
42 #include <gtk/gtk.h>
43
44 #if ENABLE(PROGRESS_TAG)
45 #include "RenderProgress.h"
46 #endif
47
48 namespace WebCore {
49
50 typedef HashMap<GType, GRefPtr<GtkStyleContext> > StyleContextMap;
51 static StyleContextMap& styleContextMap();
52
53 static void gtkStyleChangedCallback(GObject*, GParamSpec*)
54 {
55     StyleContextMap::const_iterator end = styleContextMap().end();
56     for (StyleContextMap::const_iterator iter = styleContextMap().begin(); iter != end; ++iter)
57         gtk_style_context_invalidate(iter->second.get());
58
59     Page::scheduleForcedStyleRecalcForAllPages();
60 }
61
62 static StyleContextMap& styleContextMap()
63 {
64     DEFINE_STATIC_LOCAL(StyleContextMap, map, ());
65
66     static bool initialized = false;
67     if (!initialized) {
68         GtkSettings* settings = gtk_settings_get_default();
69         g_signal_connect(settings, "notify::gtk-theme-name", G_CALLBACK(gtkStyleChangedCallback), 0);
70         g_signal_connect(settings, "notify::gtk-color-scheme", G_CALLBACK(gtkStyleChangedCallback), 0);
71         initialized = true;
72     }
73     return map;
74 }
75
76 static GtkStyleContext* getStyleContext(GType widgetType)
77 {
78     std::pair<StyleContextMap::iterator, bool> result = styleContextMap().add(widgetType, 0);
79     if (!result.second)
80         return result.first->second.get();
81
82     GtkWidgetPath* path = gtk_widget_path_new();
83     gtk_widget_path_append_type(path, widgetType);
84
85     GRefPtr<GtkStyleContext> context = adoptGRef(gtk_style_context_new());
86     gtk_style_context_set_path(context.get(), path);
87     gtk_widget_path_free(path);
88
89     result.first->second = context;
90     return context.get();
91 }
92
93 // This is not a static method, because we want to avoid having GTK+ headers in RenderThemeGtk.h.
94 extern GtkTextDirection gtkTextDirection(TextDirection);
95
96 void RenderThemeGtk::initMediaColors()
97 {
98     GtkStyle* style = gtk_widget_get_style(GTK_WIDGET(gtkContainer()));
99     m_panelColor = style->bg[GTK_STATE_NORMAL];
100     m_sliderColor = style->bg[GTK_STATE_ACTIVE];
101     m_sliderThumbColor = style->bg[GTK_STATE_SELECTED];
102 }
103
104 GtkStateType RenderThemeGtk::getGtkStateType(RenderObject* object)
105 {
106     if (!isEnabled(object) || isReadOnlyControl(object))
107         return GTK_STATE_INSENSITIVE;
108     if (isPressed(object))
109         return GTK_STATE_ACTIVE;
110     if (isHovered(object))
111         return GTK_STATE_PRELIGHT;
112     return GTK_STATE_NORMAL;
113 }
114
115 bool RenderThemeGtk::paintRenderObject(GtkThemeWidgetType type, RenderObject* renderObject, GraphicsContext* context, const IntRect& rect, int flags)
116 {
117     // Painting is disabled so just claim to have succeeded
118     if (context->paintingDisabled())
119         return false;
120
121     GtkWidgetState widgetState;
122     widgetState.active = isPressed(renderObject);
123     widgetState.focused = isFocused(renderObject);
124
125     // https://bugs.webkit.org/show_bug.cgi?id=18364
126     // The Mozilla theme drawing code, only paints a button as pressed when it's pressed 
127     // while hovered. Until we move away from the Mozila code, work-around the issue by
128     // forcing a pressed button into the hovered state. This ensures that buttons activated
129     // via the keyboard have the proper rendering.
130     widgetState.inHover = isHovered(renderObject) || (type == MOZ_GTK_BUTTON && isPressed(renderObject));
131
132     // FIXME: Disabled does not always give the correct appearance for ReadOnly
133     widgetState.disabled = !isEnabled(renderObject) || isReadOnlyControl(renderObject);
134     widgetState.isDefault = false;
135     widgetState.canDefault = false;
136     widgetState.depressed = false;
137
138     WidgetRenderingContext widgetContext(context, rect);
139     return !widgetContext.paintMozillaWidget(type, &widgetState, flags,
140                                              gtkTextDirection(renderObject->style()->direction()));
141 }
142
143 static void setToggleSize(const RenderThemeGtk* theme, RenderStyle* style, ControlPart appearance)
144 {
145     // The width and height are both specified, so we shouldn't change them.
146     if (!style->width().isIntrinsicOrAuto() && !style->height().isAuto())
147         return;
148
149     // FIXME: This is probably not correct use of indicatorSize and indicatorSpacing.
150     gint indicatorSize, indicatorSpacing;
151     theme->getIndicatorMetrics(appearance, indicatorSize, indicatorSpacing);
152
153     // Other ports hard-code this to 13, but GTK+ users tend to demand the native look.
154     // It could be made a configuration option values other than 13 actually break site compatibility.
155     int length = indicatorSize + indicatorSpacing;
156     if (style->width().isIntrinsicOrAuto())
157         style->setWidth(Length(length, Fixed));
158
159     if (style->height().isAuto())
160         style->setHeight(Length(length, Fixed));
161 }
162
163 void RenderThemeGtk::setCheckboxSize(RenderStyle* style) const
164 {
165     setToggleSize(this, style, RadioPart);
166 }
167
168 bool RenderThemeGtk::paintCheckbox(RenderObject* object, const PaintInfo& info, const IntRect& rect)
169 {
170     return paintRenderObject(MOZ_GTK_CHECKBUTTON, object, info.context, rect, isChecked(object));
171 }
172
173 void RenderThemeGtk::setRadioSize(RenderStyle* style) const
174 {
175     setToggleSize(this, style, RadioPart);
176 }
177
178 bool RenderThemeGtk::paintRadio(RenderObject* object, const PaintInfo& info, const IntRect& rect)
179 {
180     return paintRenderObject(MOZ_GTK_RADIOBUTTON, object, info.context, rect, isChecked(object));
181 }
182
183 bool RenderThemeGtk::paintButton(RenderObject* object, const PaintInfo& info, const IntRect& rect)
184 {
185     if (info.context->paintingDisabled())
186         return false;
187
188     GtkWidget* widget = gtkButton();
189     IntRect buttonRect(IntPoint(), rect.size());
190     IntRect focusRect(buttonRect);
191
192     GtkStateType state = getGtkStateType(object);
193     gtk_widget_set_state(widget, state);
194     gtk_widget_set_direction(widget, gtkTextDirection(object->style()->direction()));
195
196     if (isFocused(object)) {
197         if (isEnabled(object))
198             g_object_set(widget, "has-focus", TRUE, NULL);
199
200         gboolean interiorFocus = 0, focusWidth = 0, focusPadding = 0;
201         gtk_widget_style_get(widget,
202                              "interior-focus", &interiorFocus,
203                              "focus-line-width", &focusWidth,
204                              "focus-padding", &focusPadding, NULL);
205         // If we are using exterior focus, we shrink the button rect down before
206         // drawing. If we are using interior focus we shrink the focus rect. This
207         // approach originates from the Mozilla theme drawing code (gtk2drawing.c).
208         if (interiorFocus) {
209             GtkStyle* style = gtk_widget_get_style(widget);
210             focusRect.inflateX(-style->xthickness - focusPadding);
211             focusRect.inflateY(-style->ythickness - focusPadding);
212         } else {
213             buttonRect.inflateX(-focusWidth - focusPadding);
214             buttonRect.inflateY(-focusPadding - focusPadding);
215         }
216     }
217
218     WidgetRenderingContext widgetContext(info.context, rect);
219     GtkShadowType shadowType = state == GTK_STATE_ACTIVE ? GTK_SHADOW_IN : GTK_SHADOW_OUT;
220     widgetContext.gtkPaintBox(buttonRect, widget, state, shadowType, "button");
221     if (isFocused(object))
222         widgetContext.gtkPaintFocus(focusRect, widget, state, "button");
223
224     g_object_set(widget, "has-focus", FALSE, NULL);
225     return false;
226 }
227
228 static void getComboBoxPadding(RenderStyle* style, int& left, int& top, int& right, int& bottom)
229 {
230     // If this menu list button isn't drawn using the native theme, we
231     // don't add any extra padding beyond what WebCore already uses.
232     if (style->appearance() == NoControlPart)
233         return;
234     moz_gtk_get_widget_border(MOZ_GTK_DROPDOWN, &left, &top, &right, &bottom,
235                               gtkTextDirection(style->direction()), TRUE);
236 }
237
238 int RenderThemeGtk::popupInternalPaddingLeft(RenderStyle* style) const
239 {
240     int left = 0, top = 0, right = 0, bottom = 0;
241     getComboBoxPadding(style, left, top, right, bottom);
242     return left;
243 }
244
245 int RenderThemeGtk::popupInternalPaddingRight(RenderStyle* style) const
246 {
247     int left = 0, top = 0, right = 0, bottom = 0;
248     getComboBoxPadding(style, left, top, right, bottom);
249     return right;
250 }
251
252 int RenderThemeGtk::popupInternalPaddingTop(RenderStyle* style) const
253 {
254     int left = 0, top = 0, right = 0, bottom = 0;
255     getComboBoxPadding(style, left, top, right, bottom);
256     return top;
257 }
258
259 int RenderThemeGtk::popupInternalPaddingBottom(RenderStyle* style) const
260 {
261     int left = 0, top = 0, right = 0, bottom = 0;
262     getComboBoxPadding(style, left, top, right, bottom);
263     return bottom;
264 }
265
266 bool RenderThemeGtk::paintMenuList(RenderObject* object, const PaintInfo& info, const IntRect& rect)
267 {
268     return paintRenderObject(MOZ_GTK_DROPDOWN, object, info.context, rect);
269 }
270
271 void RenderThemeGtk::setTextInputBorders(RenderStyle* style)
272 {
273     // If this control isn't drawn using the native theme, we don't touch the borders.
274     if (style->appearance() == NoControlPart)
275         return;
276
277     // We cannot give a proper rendering when border radius is active, unfortunately.
278     style->resetBorderRadius();
279
280     int left = 0, top = 0, right = 0, bottom = 0;
281     moz_gtk_get_widget_border(MOZ_GTK_ENTRY, &left, &top, &right, &bottom,
282                               gtkTextDirection(style->direction()), TRUE);
283     style->setBorderLeftWidth(left);
284     style->setBorderTopWidth(top);
285     style->setBorderRightWidth(right);
286     style->setBorderBottomWidth(bottom);
287 }
288
289 bool RenderThemeGtk::paintTextField(RenderObject* object, const PaintInfo& info, const IntRect& rect)
290 {
291     return paintRenderObject(MOZ_GTK_ENTRY, object, info.context, rect);
292 }
293
294 bool RenderThemeGtk::paintSliderTrack(RenderObject* object, const PaintInfo& info, const IntRect& rect)
295 {
296     if (info.context->paintingDisabled())
297         return false;
298
299     ControlPart part = object->style()->appearance();
300     ASSERT(part == SliderHorizontalPart || part == SliderVerticalPart);
301
302     // We shrink the trough rect slightly to make room for the focus indicator.
303     IntRect troughRect(IntPoint(), rect.size()); // This is relative to rect.
304     GtkWidget* widget = 0;
305     if (part == SliderVerticalPart) {
306         widget = gtkVScale();
307         troughRect.inflateY(-gtk_widget_get_style(widget)->ythickness);
308     } else {
309         widget = gtkHScale();
310         troughRect.inflateX(-gtk_widget_get_style(widget)->xthickness);
311     }
312     gtk_widget_set_direction(widget, gtkTextDirection(object->style()->direction()));
313
314     WidgetRenderingContext widgetContext(info.context, rect);
315     widgetContext.gtkPaintBox(troughRect, widget, GTK_STATE_ACTIVE, GTK_SHADOW_OUT, "trough");
316     if (isFocused(object))
317         widgetContext.gtkPaintFocus(IntRect(IntPoint(), rect.size()), widget, getGtkStateType(object), "trough");
318
319     return false;
320 }
321
322 bool RenderThemeGtk::paintSliderThumb(RenderObject* object, const PaintInfo& info, const IntRect& rect)
323 {
324     if (info.context->paintingDisabled())
325         return false;
326
327     ControlPart part = object->style()->appearance();
328     ASSERT(part == SliderThumbHorizontalPart || part == SliderThumbVerticalPart);
329
330     GtkWidget* widget = 0;
331     const char* detail = 0;
332     GtkOrientation orientation;
333     if (part == SliderThumbVerticalPart) {
334         widget = gtkVScale();
335         detail = "vscale";
336         orientation = GTK_ORIENTATION_VERTICAL;
337     } else {
338         widget = gtkHScale();
339         detail = "hscale";
340         orientation = GTK_ORIENTATION_HORIZONTAL;
341     }
342     gtk_widget_set_direction(widget, gtkTextDirection(object->style()->direction()));
343
344     // Only some themes have slider thumbs respond to clicks and some don't. This information is
345     // gathered via the 'activate-slider' property, but it's deprecated in GTK+ 2.22 and removed in
346     // GTK+ 3.x. The drawback of not honoring it is that slider thumbs change color when you click
347     // on them. 
348     IntRect thumbRect(IntPoint(), rect.size());
349     WidgetRenderingContext widgetContext(info.context, rect);
350     widgetContext.gtkPaintSlider(thumbRect, widget, getGtkStateType(object), GTK_SHADOW_OUT, detail, orientation);
351     return false;
352 }
353
354 void RenderThemeGtk::adjustSliderThumbSize(RenderObject* o) const
355 {
356     ControlPart part = o->style()->appearance();
357 #if ENABLE(VIDEO)
358     if (part == MediaSliderThumbPart) {
359         o->style()->setWidth(Length(m_mediaSliderThumbWidth, Fixed));
360         o->style()->setHeight(Length(m_mediaSliderThumbHeight, Fixed));
361         return;
362     }
363     if (part == MediaVolumeSliderThumbPart)
364         return;
365 #endif
366
367     GtkWidget* widget = part == SliderThumbHorizontalPart ? gtkHScale() : gtkVScale();
368     int length = 0, width = 0;
369     gtk_widget_style_get(widget,
370                          "slider_length", &length,
371                          "slider_width", &width,
372                          NULL);
373
374     if (part == SliderThumbHorizontalPart) {
375         o->style()->setWidth(Length(length, Fixed));
376         o->style()->setHeight(Length(width, Fixed));
377         return;
378     }
379     ASSERT(part == SliderThumbVerticalPart);
380     o->style()->setWidth(Length(width, Fixed));
381     o->style()->setHeight(Length(length, Fixed));
382 }
383
384 #if ENABLE(PROGRESS_TAG)
385 bool RenderThemeGtk::paintProgressBar(RenderObject* renderObject, const PaintInfo& paintInfo, const IntRect& rect)
386 {
387     if (!renderObject->isProgress())
388         return true;
389
390     GtkWidget* progressBarWidget = moz_gtk_get_progress_widget();
391     if (!progressBarWidget)
392         return true;
393
394     if (paintRenderObject(MOZ_GTK_PROGRESSBAR, renderObject, paintInfo.context, rect))
395         return true;
396
397     IntRect chunkRect(rect);
398     RenderProgress* renderProgress = toRenderProgress(renderObject);
399
400     GtkStyle* style = gtk_widget_get_style(progressBarWidget);
401     chunkRect.setHeight(chunkRect.height() - (2 * style->ythickness));
402     chunkRect.setY(chunkRect.y() + style->ythickness);
403     chunkRect.setWidth((chunkRect.width() - (2 * style->xthickness)) * renderProgress->position());
404     if (renderObject->style()->direction() == RTL)
405         chunkRect.setX(rect.x() + rect.width() - chunkRect.width() - style->xthickness);
406     else
407         chunkRect.setX(chunkRect.x() + style->xthickness);
408
409     return paintRenderObject(MOZ_GTK_PROGRESS_CHUNK, renderObject, paintInfo.context, chunkRect);
410 }
411 #endif
412
413 GRefPtr<GdkPixbuf> RenderThemeGtk::getStockIcon(GType widgetType, const char* iconName, gint direction, gint state, gint iconSize)
414 {
415     GtkStyleContext* context = getStyleContext(widgetType);
416     GtkIconSet* iconSet = gtk_style_context_lookup_icon_set(context, iconName);
417
418     gtk_style_context_save(context);
419
420     guint flags = 0;
421     if (state == GTK_STATE_PRELIGHT)
422         flags |= GTK_STATE_FLAG_PRELIGHT;
423     else if (state == GTK_STATE_INSENSITIVE)
424         flags |= GTK_STATE_FLAG_INSENSITIVE;
425
426     gtk_style_context_set_state(context, static_cast<GtkStateFlags>(flags));
427     gtk_style_context_set_direction(context, static_cast<GtkTextDirection>(direction));
428     GdkPixbuf* icon = gtk_icon_set_render_icon_pixbuf(iconSet, context, static_cast<GtkIconSize>(iconSize));
429
430     gtk_style_context_restore(context);
431
432     return adoptGRefPtr(icon);
433 }
434
435 Color RenderThemeGtk::platformActiveSelectionBackgroundColor() const
436 {
437     GtkWidget* widget = gtkEntry();
438     return gtk_widget_get_style(widget)->base[GTK_STATE_SELECTED];
439 }
440
441 Color RenderThemeGtk::platformInactiveSelectionBackgroundColor() const
442 {
443     GtkWidget* widget = gtkEntry();
444     return gtk_widget_get_style(widget)->base[GTK_STATE_ACTIVE];
445 }
446
447 Color RenderThemeGtk::platformActiveSelectionForegroundColor() const
448 {
449     GtkWidget* widget = gtkEntry();
450     return gtk_widget_get_style(widget)->text[GTK_STATE_SELECTED];
451 }
452
453 Color RenderThemeGtk::platformInactiveSelectionForegroundColor() const
454 {
455     GtkWidget* widget = gtkEntry();
456     return gtk_widget_get_style(widget)->text[GTK_STATE_ACTIVE];
457 }
458
459 Color RenderThemeGtk::activeListBoxSelectionBackgroundColor() const
460 {
461     GtkWidget* widget = gtkTreeView();
462     return gtk_widget_get_style(widget)->base[GTK_STATE_SELECTED];
463 }
464
465 Color RenderThemeGtk::inactiveListBoxSelectionBackgroundColor() const
466 {
467     GtkWidget* widget = gtkTreeView();
468     return gtk_widget_get_style(widget)->base[GTK_STATE_ACTIVE];
469 }
470
471 Color RenderThemeGtk::activeListBoxSelectionForegroundColor() const
472 {
473     GtkWidget* widget = gtkTreeView();
474     return gtk_widget_get_style(widget)->text[GTK_STATE_SELECTED];
475 }
476
477 Color RenderThemeGtk::inactiveListBoxSelectionForegroundColor() const
478 {
479     GtkWidget* widget = gtkTreeView();
480     return gtk_widget_get_style(widget)->text[GTK_STATE_ACTIVE];
481 }
482
483 Color RenderThemeGtk::systemColor(int cssValueId) const
484 {
485     switch (cssValueId) {
486     case CSSValueButtontext:
487         return Color(gtk_widget_get_style(gtkButton())->fg[GTK_STATE_NORMAL]);
488     case CSSValueCaptiontext:
489         return Color(gtk_widget_get_style(gtkEntry())->fg[GTK_STATE_NORMAL]);
490     default:
491         return RenderTheme::systemColor(cssValueId);
492     }
493 }
494
495 static void gtkStyleSetCallback(GtkWidget* widget, GtkStyle* previous, RenderTheme* renderTheme)
496 {
497     // FIXME: Make sure this function doesn't get called many times for a single GTK+ style change signal.
498     renderTheme->platformColorsDidChange();
499 }
500
501 void RenderThemeGtk::setupWidgetAndAddToContainer(GtkWidget* widget, GtkWidget* window) const
502 {
503     gtk_container_add(GTK_CONTAINER(window), widget);
504     gtk_widget_realize(widget);
505     g_object_set_data(G_OBJECT(widget), "transparent-bg-hint", GINT_TO_POINTER(TRUE));
506
507     // FIXME: Perhaps this should only be called for the containing window or parent container.
508     g_signal_connect(widget, "style-set", G_CALLBACK(gtkStyleSetCallback), const_cast<RenderThemeGtk*>(this));
509 }
510
511 GtkWidget* RenderThemeGtk::gtkContainer() const
512 {
513     if (m_gtkContainer)
514         return m_gtkContainer;
515
516     m_gtkWindow = gtk_window_new(GTK_WINDOW_POPUP);
517     gtk_widget_realize(m_gtkWindow);
518     gtk_widget_set_name(m_gtkWindow, "MozillaGtkWidget");
519
520     m_gtkContainer = gtk_fixed_new();
521     setupWidgetAndAddToContainer(m_gtkContainer, m_gtkWindow);
522     return m_gtkContainer;
523 }
524
525 GtkWidget* RenderThemeGtk::gtkButton() const
526 {
527     if (m_gtkButton)
528         return m_gtkButton;
529     m_gtkButton = gtk_button_new();
530     setupWidgetAndAddToContainer(m_gtkButton, gtkContainer());
531     return m_gtkButton;
532 }
533
534 GtkWidget* RenderThemeGtk::gtkEntry() const
535 {
536     if (m_gtkEntry)
537         return m_gtkEntry;
538     m_gtkEntry = gtk_entry_new();
539     setupWidgetAndAddToContainer(m_gtkEntry, gtkContainer());
540     return m_gtkEntry;
541 }
542
543 GtkWidget* RenderThemeGtk::gtkTreeView() const
544 {
545     if (m_gtkTreeView)
546         return m_gtkTreeView;
547     m_gtkTreeView = gtk_tree_view_new();
548     setupWidgetAndAddToContainer(m_gtkTreeView, gtkContainer());
549     return m_gtkTreeView;
550 }
551
552 GtkWidget* RenderThemeGtk::gtkVScale() const
553 {
554     if (m_gtkVScale)
555         return m_gtkVScale;
556     m_gtkVScale = gtk_vscale_new(0);
557     setupWidgetAndAddToContainer(m_gtkVScale, gtkContainer());
558     return m_gtkVScale;
559 }
560
561 GtkWidget* RenderThemeGtk::gtkHScale() const
562 {
563     if (m_gtkHScale)
564         return m_gtkHScale;
565     m_gtkHScale = gtk_hscale_new(0);
566     setupWidgetAndAddToContainer(m_gtkHScale, gtkContainer());
567     return m_gtkHScale;
568 }
569
570 GtkWidget* RenderThemeGtk::gtkScrollbar()
571 {
572     return moz_gtk_get_scrollbar_widget();
573 }
574
575 } // namespace WebCore
576
577 #endif // !GTK_API_VERSION_2