4842d68c345dee0b08a9b63aed24382b9db7d6f5
[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  *
7  * This library is free software; you can redistribute it and/or
8  * modify it under the terms of the GNU Library General Public
9  * License as published by the Free Software Foundation; either
10  * version 2 of the License, or (at your option) any later version.
11  *
12  * This library is distributed in the hope that it will be useful,
13  * but WITHOUT ANY WARRANTY; without even the implied warranty of
14  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
15  * Library General Public License for more details.
16  *
17  * You should have received a copy of the GNU Library General Public License
18  * along with this library; see the file COPYING.LIB.  If not, write to
19  * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
20  * Boston, MA 02110-1301, USA.
21  *
22  */
23
24 #include "config.h"
25 #include "RenderThemeGtk.h"
26
27 #include "TransformationMatrix.h"
28 #include "GraphicsContext.h"
29 #include "NotImplemented.h"
30 #include "RenderBox.h"
31 #include "RenderObject.h"
32 #include "gtkdrawing.h"
33
34 #include <gdk/gdk.h>
35
36 namespace WebCore {
37
38 PassRefPtr<RenderTheme> RenderThemeGtk::create()
39 {
40     return adoptRef(new RenderThemeGtk());
41 }
42
43 PassRefPtr<RenderTheme> RenderTheme::themeForPage(Page* page)
44 {
45     static RenderTheme* rt = RenderThemeGtk::create().releaseRef();
46     return rt;
47 }
48
49 static int mozGtkRefCount = 0;
50
51 RenderThemeGtk::RenderThemeGtk()
52     : m_gtkWindow(0)
53     , m_gtkContainer(0)
54     , m_gtkEntry(0)
55     , m_gtkTreeView(0)
56 {
57     if (!mozGtkRefCount)
58         moz_gtk_init();
59
60     ++mozGtkRefCount;
61 }
62
63 RenderThemeGtk::~RenderThemeGtk()
64 {
65     --mozGtkRefCount;
66
67     if (!mozGtkRefCount)
68         moz_gtk_shutdown();
69 }
70
71 static bool supportsFocus(ControlPart appearance)
72 {
73     switch (appearance) {
74         case PushButtonPart:
75         case ButtonPart:
76         case TextFieldPart:
77         case TextAreaPart:
78         case SearchFieldPart:
79         case MenulistPart:
80         case RadioPart:
81         case CheckboxPart:
82             return true;
83         default:
84             return false;
85     }
86 }
87
88 bool RenderThemeGtk::supportsFocusRing(const RenderStyle* style) const
89 {
90     return supportsFocus(style->appearance());
91 }
92
93 bool RenderThemeGtk::controlSupportsTints(const RenderObject* o) const
94 {
95     return isEnabled(o);
96 }
97
98 int RenderThemeGtk::baselinePosition(const RenderObject* o) const
99 {
100     if (!o->isBox())
101         return 0;
102
103     // FIXME: This strategy is possibly incorrect for the GTK+ port.
104     if (o->style()->appearance() == CheckboxPart ||
105         o->style()->appearance() == RadioPart) {
106         const RenderBox* box = toRenderBox(o);
107         return box->marginTop() + box->height() - 2;
108     }
109
110     return RenderTheme::baselinePosition(o);
111 }
112
113 static GtkTextDirection gtkTextDirection(TextDirection direction)
114 {
115     switch (direction) {
116     case RTL:
117         return GTK_TEXT_DIR_RTL;
118     case LTR:
119         return GTK_TEXT_DIR_LTR;
120     default:
121         return GTK_TEXT_DIR_NONE;
122     }
123 }
124
125 static void adjustMozStyle(RenderStyle* style, GtkThemeWidgetType type)
126 {
127     gint left, top, right, bottom;
128     GtkTextDirection direction = gtkTextDirection(style->direction());
129     gboolean inhtml = true;
130
131     if (moz_gtk_get_widget_border(type, &left, &top, &right, &bottom, direction, inhtml) != MOZ_GTK_SUCCESS)
132         return;
133
134     // FIXME: This approach is likely to be incorrect. See other ports and layout tests to see the problem.
135     const int xpadding = 1;
136     const int ypadding = 1;
137
138     style->setPaddingLeft(Length(xpadding + left, Fixed));
139     style->setPaddingTop(Length(ypadding + top, Fixed));
140     style->setPaddingRight(Length(xpadding + right, Fixed));
141     style->setPaddingBottom(Length(ypadding + bottom, Fixed));
142 }
143
144 static void setMozState(RenderTheme* theme, GtkWidgetState* state, RenderObject* o)
145 {
146     state->active = theme->isPressed(o);
147     state->focused = theme->isFocused(o);
148     state->inHover = theme->isHovered(o);
149     // FIXME: Disabled does not always give the correct appearance for ReadOnly
150     state->disabled = !theme->isEnabled(o) || theme->isReadOnlyControl(o);
151     state->isDefault = false;
152     state->canDefault = false;
153     state->depressed = false;
154 }
155
156 static bool paintMozWidget(RenderTheme* theme, GtkThemeWidgetType type, RenderObject* o, const RenderObject::PaintInfo& i, const IntRect& rect)
157 {
158     // No GdkWindow to render to, so return true to fall back
159     if (!i.context->gdkDrawable())
160         return true;
161
162     // Painting is disabled so just claim to have succeeded
163     if (i.context->paintingDisabled())
164         return false;
165
166     GtkWidgetState mozState;
167     setMozState(theme, &mozState, o);
168
169     int flags;
170
171     // We might want to make setting flags the caller's job at some point rather than doing it here.
172     switch (type) {
173         case MOZ_GTK_BUTTON:
174             flags = GTK_RELIEF_NORMAL;
175             break;
176         case MOZ_GTK_CHECKBUTTON:
177         case MOZ_GTK_RADIOBUTTON:
178             flags = theme->isChecked(o);
179             break;
180         default:
181             flags = 0;
182             break;
183     }
184
185     TransformationMatrix ctm = i.context->getCTM();
186
187     IntPoint pos = ctm.mapPoint(rect.location());
188     GdkRectangle gdkRect = IntRect(pos.x(), pos.y(), rect.width(), rect.height());
189     GtkTextDirection direction = gtkTextDirection(o->style()->direction());
190
191     // Find the clip rectangle
192     cairo_t *cr = i.context->platformContext();
193     double clipX1, clipX2, clipY1, clipY2;
194     cairo_clip_extents(cr, &clipX1, &clipY1, &clipX2, &clipY2);
195
196     GdkRectangle gdkClipRect;
197     gdkClipRect.width = clipX2 - clipX1;
198     gdkClipRect.height = clipY2 - clipY1;
199     IntPoint clipPos = ctm.mapPoint(IntPoint(clipX1, clipY1));
200     gdkClipRect.x = clipPos.x();
201     gdkClipRect.y = clipPos.y();
202
203     gdk_rectangle_intersect(&gdkRect, &gdkClipRect, &gdkClipRect);
204
205     return moz_gtk_widget_paint(type, i.context->gdkDrawable(), &gdkRect, &gdkClipRect, &mozState, flags, direction) != MOZ_GTK_SUCCESS;
206 }
207
208 static void setButtonPadding(RenderStyle* style)
209 {
210     // FIXME: This looks incorrect.
211     const int padding = 8;
212     style->setPaddingLeft(Length(padding, Fixed));
213     style->setPaddingRight(Length(padding, Fixed));
214     style->setPaddingTop(Length(padding / 2, Fixed));
215     style->setPaddingBottom(Length(padding / 2, Fixed));
216 }
217
218 static void setToggleSize(RenderStyle* style, ControlPart appearance)
219 {
220     // The width and height are both specified, so we shouldn't change them.
221     if (!style->width().isIntrinsicOrAuto() && !style->height().isAuto())
222         return;
223
224     // FIXME: This is probably not correct use of indicator_size and indicator_spacing.
225     gint indicator_size, indicator_spacing;
226
227     switch (appearance) {
228         case CheckboxPart:
229             if (moz_gtk_checkbox_get_metrics(&indicator_size, &indicator_spacing) != MOZ_GTK_SUCCESS)
230                 return;
231             break;
232         case RadioPart:
233             if (moz_gtk_radio_get_metrics(&indicator_size, &indicator_spacing) != MOZ_GTK_SUCCESS)
234                 return;
235             break;
236         default:
237             return;
238     }
239
240     // Other ports hard-code this to 13, but GTK+ users tend to demand the native look.
241     // It could be made a configuration option values other than 13 actually break site compatibility.
242     int length = indicator_size + indicator_spacing;
243     if (style->width().isIntrinsicOrAuto())
244         style->setWidth(Length(length, Fixed));
245
246     if (style->height().isAuto())
247         style->setHeight(Length(length, Fixed));
248 }
249
250 void RenderThemeGtk::setCheckboxSize(RenderStyle* style) const
251 {
252     setToggleSize(style, RadioPart);
253 }
254
255 bool RenderThemeGtk::paintCheckbox(RenderObject* o, const RenderObject::PaintInfo& i, const IntRect& rect)
256 {
257     return paintMozWidget(this, MOZ_GTK_CHECKBUTTON, o, i, rect);
258 }
259
260 void RenderThemeGtk::setRadioSize(RenderStyle* style) const
261 {
262     setToggleSize(style, RadioPart);
263 }
264
265 bool RenderThemeGtk::paintRadio(RenderObject* o, const RenderObject::PaintInfo& i, const IntRect& rect)
266 {
267     return paintMozWidget(this, MOZ_GTK_RADIOBUTTON, o, i, rect);
268 }
269
270 void RenderThemeGtk::adjustButtonStyle(CSSStyleSelector* selector, RenderStyle* style, WebCore::Element* e) const
271 {
272     // FIXME: Is this condition necessary?
273     if (style->appearance() == PushButtonPart) {
274         style->resetBorder();
275         style->setHeight(Length(Auto));
276         style->setWhiteSpace(PRE);
277         setButtonPadding(style);
278     } else {
279         // FIXME: This should not be hard-coded.
280         style->setMinHeight(Length(14, Fixed));
281         style->resetBorderTop();
282         style->resetBorderBottom();
283     }
284 }
285
286 bool RenderThemeGtk::paintButton(RenderObject* o, const RenderObject::PaintInfo& i, const IntRect& rect)
287 {
288     return paintMozWidget(this, MOZ_GTK_BUTTON, o, i, rect);
289 }
290
291 void RenderThemeGtk::adjustMenuListStyle(CSSStyleSelector* selector, RenderStyle* style, WebCore::Element* e) const
292 {
293     style->resetBorder();
294     style->resetPadding();
295     style->setHeight(Length(Auto));
296     style->setWhiteSpace(PRE);
297     adjustMozStyle(style, MOZ_GTK_DROPDOWN);
298 }
299
300 bool RenderThemeGtk::paintMenuList(RenderObject* o, const RenderObject::PaintInfo& i, const IntRect& rect)
301 {
302     return paintMozWidget(this, MOZ_GTK_DROPDOWN, o, i, rect);
303 }
304
305 void RenderThemeGtk::adjustTextFieldStyle(CSSStyleSelector* selector, RenderStyle* style, Element* e) const
306 {
307     style->resetBorder();
308     style->resetPadding();
309     style->setHeight(Length(Auto));
310     style->setWhiteSpace(PRE);
311     adjustMozStyle(style, MOZ_GTK_ENTRY);
312 }
313
314 bool RenderThemeGtk::paintTextField(RenderObject* o, const RenderObject::PaintInfo& i, const IntRect& rect)
315 {
316     return paintMozWidget(this, MOZ_GTK_ENTRY, o, i, rect);
317 }
318
319 bool RenderThemeGtk::paintTextArea(RenderObject* o, const RenderObject::PaintInfo& i, const IntRect& r)
320 {
321     return paintTextField(o, i, r);
322 }
323
324 void RenderThemeGtk::adjustSearchFieldResultsButtonStyle(CSSStyleSelector* selector, RenderStyle* style, Element* e) const
325 {
326     adjustSearchFieldCancelButtonStyle(selector, style, e);
327 }
328
329 bool RenderThemeGtk::paintSearchFieldResultsButton(RenderObject* o, const RenderObject::PaintInfo& i, const IntRect& rect)
330 {
331     return paintMozWidget(this, MOZ_GTK_DROPDOWN_ARROW, o, i, rect);
332 }
333
334 void RenderThemeGtk::adjustSearchFieldResultsDecorationStyle(CSSStyleSelector* selector, RenderStyle* style, Element* e) const
335 {
336     style->resetBorder();
337     style->resetPadding();
338
339     // FIXME: This should not be hard-coded.
340     IntSize size = IntSize(14, 14);
341     style->setWidth(Length(size.width(), Fixed));
342     style->setHeight(Length(size.height(), Fixed));
343 }
344
345 bool RenderThemeGtk::paintSearchFieldResultsDecoration(RenderObject* o, const RenderObject::PaintInfo& i, const IntRect& rect)
346 {
347     return paintMozWidget(this, MOZ_GTK_CHECKMENUITEM, o, i, rect);
348 }
349
350 void RenderThemeGtk::adjustSearchFieldCancelButtonStyle(CSSStyleSelector* selector, RenderStyle* style, Element* e) const
351 {
352     style->resetBorder();
353     style->resetPadding();
354
355     // FIXME: This should not be hard-coded.
356     IntSize size = IntSize(14, 14);
357     style->setWidth(Length(size.width(), Fixed));
358     style->setHeight(Length(size.height(), Fixed));
359 }
360
361 bool RenderThemeGtk::paintSearchFieldCancelButton(RenderObject* o, const RenderObject::PaintInfo& i, const IntRect& rect)
362 {
363     return paintMozWidget(this, MOZ_GTK_CHECKMENUITEM, o, i, rect);
364 }
365
366 void RenderThemeGtk::adjustSearchFieldStyle(CSSStyleSelector* selector, RenderStyle* style, Element* e) const
367 {
368     adjustTextFieldStyle(selector, style, e);
369 }
370
371 bool RenderThemeGtk::paintSearchField(RenderObject* o, const RenderObject::PaintInfo& i, const IntRect& rect)
372 {
373     return paintTextField(o, i, rect);
374 }
375
376 Color RenderThemeGtk::platformActiveSelectionBackgroundColor() const
377 {
378     GtkWidget* widget = gtkEntry();
379     return widget->style->base[GTK_STATE_SELECTED];
380 }
381
382 Color RenderThemeGtk::platformInactiveSelectionBackgroundColor() const
383 {
384     GtkWidget* widget = gtkEntry();
385     return widget->style->base[GTK_STATE_ACTIVE];
386 }
387
388 Color RenderThemeGtk::platformActiveSelectionForegroundColor() const
389 {
390     GtkWidget* widget = gtkEntry();
391     return widget->style->text[GTK_STATE_SELECTED];
392 }
393
394 Color RenderThemeGtk::platformInactiveSelectionForegroundColor() const
395 {
396     GtkWidget* widget = gtkEntry();
397     return widget->style->text[GTK_STATE_ACTIVE];
398 }
399
400 Color RenderThemeGtk::activeListBoxSelectionBackgroundColor() const
401 {
402     GtkWidget* widget = gtkTreeView();
403     return widget->style->base[GTK_STATE_SELECTED];
404 }
405
406 Color RenderThemeGtk::inactiveListBoxSelectionBackgroundColor() const
407 {
408     GtkWidget* widget = gtkTreeView();
409     return widget->style->base[GTK_STATE_ACTIVE];
410 }
411
412 Color RenderThemeGtk::activeListBoxSelectionForegroundColor() const
413 {
414     GtkWidget* widget = gtkTreeView();
415     return widget->style->text[GTK_STATE_SELECTED];
416 }
417
418 Color RenderThemeGtk::inactiveListBoxSelectionForegroundColor() const
419 {
420     GtkWidget* widget = gtkTreeView();
421     return widget->style->text[GTK_STATE_ACTIVE];
422 }
423
424 double RenderThemeGtk::caretBlinkInterval() const
425 {
426     GtkSettings* settings = gtk_settings_get_default();
427
428     gboolean shouldBlink;
429     gint time;
430
431     g_object_get(settings, "gtk-cursor-blink", &shouldBlink, "gtk-cursor-blink-time", &time, NULL);
432
433     if (!shouldBlink)
434         return 0;
435
436     return time / 2000.;
437 }
438
439 void RenderThemeGtk::systemFont(int, FontDescription&) const
440 {
441     // If you remove this notImplemented(), replace it with an comment that explains why.
442     notImplemented();
443 }
444
445 static void gtkStyleSetCallback(GtkWidget* widget, GtkStyle* previous, RenderTheme* renderTheme)
446 {
447     // FIXME: Make sure this function doesn't get called many times for a single GTK+ style change signal.
448     renderTheme->platformColorsDidChange();
449 }
450
451 GtkContainer* RenderThemeGtk::gtkContainer() const
452 {
453     if (m_gtkContainer)
454         return m_gtkContainer;
455
456     m_gtkWindow = gtk_window_new(GTK_WINDOW_POPUP);
457     m_gtkContainer = GTK_CONTAINER(gtk_fixed_new());
458     gtk_container_add(GTK_CONTAINER(m_gtkWindow), GTK_WIDGET(m_gtkContainer));
459     gtk_widget_realize(m_gtkWindow);
460
461     return m_gtkContainer;
462 }
463
464 GtkWidget* RenderThemeGtk::gtkEntry() const
465 {
466     if (m_gtkEntry)
467         return m_gtkEntry;
468
469     m_gtkEntry = gtk_entry_new();
470     g_signal_connect(m_gtkEntry, "style-set", G_CALLBACK(gtkStyleSetCallback), const_cast<RenderThemeGtk*>(this));
471     gtk_container_add(gtkContainer(), m_gtkEntry);
472     gtk_widget_realize(m_gtkEntry);
473
474     return m_gtkEntry;
475 }
476
477 GtkWidget* RenderThemeGtk::gtkTreeView() const
478 {
479     if (m_gtkTreeView)
480         return m_gtkTreeView;
481
482     m_gtkTreeView = gtk_tree_view_new();
483     g_signal_connect(m_gtkTreeView, "style-set", G_CALLBACK(gtkStyleSetCallback), const_cast<RenderThemeGtk*>(this));
484     gtk_container_add(gtkContainer(), m_gtkTreeView);
485     gtk_widget_realize(m_gtkTreeView);
486
487     return m_gtkTreeView;
488 }
489
490 }