4bf30c01b97b56a836a511b43dc4999e32e5b306
[WebKit-https.git] / WebCore / rendering / RenderThemeChromiumGtk.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) 2008, 2009 Google Inc.
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 "RenderThemeChromiumGtk.h"
26
27 #include "ChromiumBridge.h"
28 #include "CSSValueKeywords.h"
29 #include "GraphicsContext.h"
30 #include "NotImplemented.h"
31 #include "PlatformContextSkia.h"
32 #include "RenderObject.h"
33 #include "ScrollbarTheme.h"
34 #include "gtkdrawing.h"
35 #include "GdkSkia.h"
36 #include "TransformationMatrix.h"
37 #include "UserAgentStyleSheets.h"
38
39 #include <gdk/gdk.h>
40
41 namespace WebCore {
42
43 enum PaddingType {
44     TopPadding,
45     RightPadding,
46     BottomPadding,
47     LeftPadding
48 };
49
50 static const int styledMenuListInternalPadding[4] = { 1, 4, 1, 4 };
51
52 // The default variable-width font size.  We use this as the default font
53 // size for the "system font", and as a base size (which we then shrink) for
54 // form control fonts.
55 static float DefaultFontSize = 16.0;
56
57 static Color makeColor(const GdkColor& c)
58 {
59     return Color(makeRGB(c.red >> 8, c.green >> 8, c.blue >> 8));
60 }
61
62 // We aim to match IE here.
63 // -IE uses a font based on the encoding as the default font for form controls.
64 // -Gecko uses MS Shell Dlg (actually calls GetStockObject(DEFAULT_GUI_FONT),
65 // which returns MS Shell Dlg)
66 // -Safari uses Lucida Grande.
67 //
68 // FIXME: The only case where we know we don't match IE is for ANSI encodings.
69 // IE uses MS Shell Dlg there, which we render incorrectly at certain pixel
70 // sizes (e.g. 15px). So, for now we just use Arial.
71 static const char* defaultGUIFont(Document* document)
72 {
73     return "Arial";
74 }
75
76 // Converts points to pixels.  One point is 1/72 of an inch.
77 static float pointsToPixels(float points)
78 {
79     static float pixelsPerInch = 0.0f;
80     if (!pixelsPerInch) {
81         GdkScreen* screen = gdk_screen_get_default();
82         // FIXME:  I'm getting floating point values of ~75 and ~100,
83         // and it's making my fonts look all wrong.  Figure this out.
84 #if 0
85         if (screen)
86             pixelsPerInch = gdk_screen_get_resolution(screen);
87         else
88 #endif
89             pixelsPerInch = 96.0f; // Match the default we set on Windows.
90     }
91
92     static const float pointsPerInch = 72.0f;
93     return points / pointsPerInch * pixelsPerInch;
94 }
95
96 static void setSizeIfAuto(RenderStyle* style, const IntSize& size)
97 {
98     if (style->width().isIntrinsicOrAuto())
99         style->setWidth(Length(size.width(), Fixed));
100     if (style->height().isAuto())
101         style->setHeight(Length(size.height(), Fixed));
102 }
103
104 static bool supportsFocus(ControlPart appearance)
105 {
106     switch (appearance) {
107     case PushButtonPart:
108     case ButtonPart:
109     case TextFieldPart:
110     case TextAreaPart:
111     case SearchFieldPart:
112     case MenulistPart:
113     case RadioPart:
114     case CheckboxPart:
115         return true;
116     default:
117         return false;
118     }
119 }
120
121 static GtkTextDirection gtkTextDirection(TextDirection direction)
122 {
123     switch (direction) {
124     case RTL:
125         return GTK_TEXT_DIR_RTL;
126     case LTR:
127         return GTK_TEXT_DIR_LTR;
128     default:
129         return GTK_TEXT_DIR_NONE;
130     }
131 }
132
133 static void setMozState(RenderTheme* theme, GtkWidgetState* state, RenderObject* o)
134 {
135     state->active = theme->isPressed(o);
136     state->focused = theme->isFocused(o);
137     state->inHover = theme->isHovered(o);
138     // FIXME: Disabled does not always give the correct appearance for ReadOnly
139     state->disabled = !theme->isEnabled(o) || theme->isReadOnlyControl(o);
140     state->isDefault = false;
141     state->canDefault = false;
142     state->depressed = false;
143 }
144
145 static bool paintMozWidget(RenderTheme* theme, GtkThemeWidgetType type, RenderObject* o, const RenderObject::PaintInfo& i, const IntRect& rect)
146 {
147     // Painting is disabled so just claim to have succeeded
148     if (i.context->paintingDisabled())
149         return false;
150
151     GtkWidgetState mozState;
152     setMozState(theme, &mozState, o);
153
154     int flags;
155
156     // We might want to make setting flags the caller's job at some point rather than doing it here.
157     switch (type) {
158     case MOZ_GTK_BUTTON:
159         flags = GTK_RELIEF_NORMAL;
160         break;
161     case MOZ_GTK_CHECKBUTTON:
162     case MOZ_GTK_RADIOBUTTON:
163         flags = theme->isChecked(o);
164         break;
165     default:
166         flags = 0;
167         break;
168     }
169
170     PlatformContextSkia* pcs = i.context->platformContext();
171     SkCanvas* canvas = pcs->canvas();
172     if (!canvas)
173         return false;
174
175     GdkRectangle gdkRect;
176     gdkRect.x = rect.x();
177     gdkRect.y = rect.y();
178     gdkRect.width = rect.width();
179     gdkRect.height = rect.height();
180
181     // getTotalClip returns the currently set clip region in device coordinates,
182     // so we have to apply the current transform (actually we only support translations)
183     // to get the page coordinates that our gtk widget rendering expects.
184     // We invert it because we want to map from device coordinates to page coordinates.
185     const SkIRect clipRegion = canvas->getTotalClip().getBounds();
186     TransformationMatrix ctm = i.context->getCTM().inverse();
187     IntPoint pos = ctm.mapPoint(IntPoint(SkScalarRound(clipRegion.fLeft), SkScalarRound(clipRegion.fTop)));
188     GdkRectangle gdkClipRect;
189     gdkClipRect.x = pos.x();
190     gdkClipRect.y = pos.y();
191     gdkClipRect.width = clipRegion.width();
192     gdkClipRect.height = clipRegion.height();
193
194     // moz_gtk_widget_paint will paint outside the bounds of gdkRect unless we further restrict |gdkClipRect|.
195     gdk_rectangle_intersect(&gdkRect, &gdkClipRect, &gdkClipRect);
196
197     GtkTextDirection direction = gtkTextDirection(o->style()->direction());
198
199     return moz_gtk_widget_paint(type, pcs->gdk_skia(), &gdkRect, &gdkClipRect, &mozState, flags, direction) != MOZ_GTK_SUCCESS;
200 }
201
202 static void gtkStyleSetCallback(GtkWidget* widget, GtkStyle* previous, RenderTheme* renderTheme)
203 {
204     // FIXME: Make sure this function doesn't get called many times for a single GTK+ style change signal.
205     renderTheme->platformColorsDidChange();
206 }
207
208 static double querySystemBlinkInterval(double defaultInterval)
209 {
210     GtkSettings* settings = gtk_settings_get_default();
211
212     gboolean shouldBlink;
213     gint time;
214
215     g_object_get(settings, "gtk-cursor-blink", &shouldBlink, "gtk-cursor-blink-time", &time, NULL);
216
217     if (!shouldBlink)
218         return 0;
219
220     return time / 1000.0;
221 }
222
223 // Implement WebCore::theme() for getting the global RenderTheme.
224 RenderTheme* theme()
225 {
226     static RenderThemeChromiumGtk gtkTheme;
227     return &gtkTheme;
228 }
229
230 RenderThemeChromiumGtk::RenderThemeChromiumGtk()
231     : m_gtkWindow(0)
232     , m_gtkContainer(0)
233     , m_gtkEntry(0)
234     , m_gtkTreeView(0)
235 {
236 }
237
238 // Use the Windows style sheets to match their metrics.
239 String RenderThemeChromiumGtk::extraDefaultStyleSheet()
240 {
241     return String(themeWinUserAgentStyleSheet, sizeof(themeWinUserAgentStyleSheet));
242 }
243
244 String RenderThemeChromiumGtk::extraQuirksStyleSheet()
245 {
246     return String(themeWinQuirksUserAgentStyleSheet, sizeof(themeWinQuirksUserAgentStyleSheet));
247 }
248
249 bool RenderThemeChromiumGtk::supportsFocusRing(const RenderStyle* style) const
250 {
251     return supportsFocus(style->appearance());
252 }
253
254 Color RenderThemeChromiumGtk::platformActiveSelectionBackgroundColor() const
255 {
256     GtkWidget* widget = gtkEntry();
257     return makeColor(widget->style->base[GTK_STATE_SELECTED]);
258 }
259
260 Color RenderThemeChromiumGtk::platformInactiveSelectionBackgroundColor() const
261 {
262     GtkWidget* widget = gtkEntry();
263     return makeColor(widget->style->base[GTK_STATE_ACTIVE]);
264 }
265
266 Color RenderThemeChromiumGtk::platformActiveSelectionForegroundColor() const
267 {
268     GtkWidget* widget = gtkEntry();
269     return makeColor(widget->style->text[GTK_STATE_SELECTED]);
270 }
271
272 Color RenderThemeChromiumGtk::platformInactiveSelectionForegroundColor() const
273 {
274     GtkWidget* widget = gtkEntry();
275     return makeColor(widget->style->text[GTK_STATE_ACTIVE]);
276 }
277
278 Color RenderThemeChromiumGtk::platformTextSearchHighlightColor() const
279 {
280     return Color(255, 255, 150);
281 }
282
283 double RenderThemeChromiumGtk::caretBlinkInterval() const
284 {
285     // Disable the blinking caret in layout test mode, as it introduces
286     // a race condition for the pixel tests. http://b/1198440
287     if (ChromiumBridge::layoutTestMode())
288         return 0;
289
290     // We cache the interval so we don't have to repeatedly request it from gtk.
291     static double blinkInterval = querySystemBlinkInterval(RenderTheme::caretBlinkInterval());
292     return blinkInterval;
293 }
294
295 void RenderThemeChromiumGtk::systemFont(int propId, Document* document, FontDescription& fontDescription) const
296 {
297     const char* faceName = 0;
298     float fontSize = 0;
299     // FIXME: see also RenderThemeChromiumWin.cpp
300     switch (propId) {
301     case CSSValueMenu:
302     case CSSValueStatusBar:
303     case CSSValueSmallCaption:
304         // triggered by LayoutTests/fast/css/css2-system-fonts.html
305         notImplemented();
306         break;
307     case CSSValueWebkitMiniControl:
308     case CSSValueWebkitSmallControl:
309     case CSSValueWebkitControl:
310         faceName = defaultGUIFont(document);
311         // Why 2 points smaller?  Because that's what Gecko does.
312         fontSize = DefaultFontSize - pointsToPixels(2);
313         break;
314     default:
315         faceName = defaultGUIFont(document);
316         fontSize = DefaultFontSize;
317     }
318
319     // Only update if the size makes sense.
320     if (fontSize > 0) {
321         fontDescription.firstFamily().setFamily(faceName);
322         fontDescription.setSpecifiedSize(fontSize);
323         fontDescription.setIsAbsoluteSize(true);
324         fontDescription.setGenericFamily(FontDescription::NoFamily);
325         fontDescription.setWeight(FontWeightNormal);
326         fontDescription.setItalic(false);
327     }
328 }
329
330 int RenderThemeChromiumGtk::minimumMenuListSize(RenderStyle* style) const
331 {
332     return 0;
333 }
334
335 bool RenderThemeChromiumGtk::paintCheckbox(RenderObject* o, const RenderObject::PaintInfo& i, const IntRect& rect)
336 {
337     return paintMozWidget(this, MOZ_GTK_CHECKBUTTON, o, i, rect);
338 }
339
340 void RenderThemeChromiumGtk::setCheckboxSize(RenderStyle* style) const
341 {
342     // If the width and height are both specified, then we have nothing to do.
343     if (!style->width().isIntrinsicOrAuto() && !style->height().isAuto())
344         return;
345
346     // FIXME:  A hard-coded size of 13 is used.  This is wrong but necessary for now.  It matches Firefox.
347     // At different DPI settings on Windows, querying the theme gives you a larger size that accounts for
348     // the higher DPI.  Until our entire engine honors a DPI setting other than 96, we can't rely on the theme's
349     // metrics.
350     const IntSize size(13, 13);
351     setSizeIfAuto(style, size);
352 }
353
354 bool RenderThemeChromiumGtk::paintRadio(RenderObject* o, const RenderObject::PaintInfo& i, const IntRect& rect)
355 {
356     return paintMozWidget(this, MOZ_GTK_RADIOBUTTON, o, i, rect);
357 }
358
359 void RenderThemeChromiumGtk::setRadioSize(RenderStyle* style) const
360 {
361     // Use same sizing for radio box as checkbox.
362     setCheckboxSize(style);
363 }
364
365 bool RenderThemeChromiumGtk::paintButton(RenderObject* o, const RenderObject::PaintInfo& i, const IntRect& rect)
366 {
367     return paintMozWidget(this, MOZ_GTK_BUTTON, o, i, rect);
368 }
369
370 bool RenderThemeChromiumGtk::paintTextField(RenderObject* o, const RenderObject::PaintInfo& i, const IntRect& rect)
371 {
372     return paintMozWidget(this, MOZ_GTK_ENTRY, o, i, rect);
373 }
374
375 bool RenderThemeChromiumGtk::paintSearchField(RenderObject* o, const RenderObject::PaintInfo& i, const IntRect& rect)
376 {
377     return paintTextField(o, i, rect);
378 }
379
380 bool RenderThemeChromiumGtk::paintSearchFieldResultsDecoration(RenderObject* o, const RenderObject::PaintInfo& i, const IntRect& rect)
381 {
382     return paintMozWidget(this, MOZ_GTK_CHECKMENUITEM, o, i, rect);
383 }
384
385 bool RenderThemeChromiumGtk::paintSearchFieldResultsButton(RenderObject* o, const RenderObject::PaintInfo& i, const IntRect& rect)
386 {
387     return paintMozWidget(this, MOZ_GTK_DROPDOWN_ARROW, o, i, rect);
388 }
389
390 bool RenderThemeChromiumGtk::paintSearchFieldCancelButton(RenderObject* o, const RenderObject::PaintInfo& i, const IntRect& rect)
391 {
392     return paintMozWidget(this, MOZ_GTK_CHECKMENUITEM, o, i, rect);
393 }
394
395 void RenderThemeChromiumGtk::adjustMenuListStyle(CSSStyleSelector* selector, RenderStyle* style, WebCore::Element* e) const
396 {
397     // Height is locked to auto on all browsers.
398     style->setLineHeight(RenderStyle::initialLineHeight());
399 }
400
401 bool RenderThemeChromiumGtk::paintMenuList(RenderObject* o, const RenderObject::PaintInfo& i, const IntRect& rect)
402 {
403     return paintMozWidget(this, MOZ_GTK_DROPDOWN, o, i, rect);
404 }
405
406 void RenderThemeChromiumGtk::adjustMenuListButtonStyle(CSSStyleSelector* selector, RenderStyle* style, Element* e) const
407 {
408     adjustMenuListStyle(selector, style, e);
409 }
410
411 // Used to paint styled menulists (i.e. with a non-default border)
412 bool RenderThemeChromiumGtk::paintMenuListButton(RenderObject* o, const RenderObject::PaintInfo& i, const IntRect& r)
413 {
414     return paintMenuList(o, i, r);
415 }
416
417 int RenderThemeChromiumGtk::popupInternalPaddingLeft(RenderStyle* style) const
418 {
419     return menuListInternalPadding(style, LeftPadding);
420 }
421
422 int RenderThemeChromiumGtk::popupInternalPaddingRight(RenderStyle* style) const
423 {
424     return menuListInternalPadding(style, RightPadding);
425 }
426
427 int RenderThemeChromiumGtk::popupInternalPaddingTop(RenderStyle* style) const
428 {
429     return menuListInternalPadding(style, TopPadding);
430 }
431
432 int RenderThemeChromiumGtk::popupInternalPaddingBottom(RenderStyle* style) const
433 {
434     return menuListInternalPadding(style, BottomPadding);
435 }
436
437 int RenderThemeWin::buttonInternalPaddingLeft() const
438 {
439     return 3;
440 }
441
442 int RenderThemeWin::buttonInternalPaddingRight() const
443 {
444     return 3;
445 }
446
447 int RenderThemeWin::buttonInternalPaddingTop() const
448 {
449     return 1;
450 }
451
452 int RenderThemeWin::buttonInternalPaddingBottom() const
453 {
454     return 1;
455 }
456
457 bool RenderThemeChromiumGtk::controlSupportsTints(const RenderObject* o) const
458 {
459     return isEnabled(o);
460 }
461
462 Color RenderThemeChromiumGtk::activeListBoxSelectionBackgroundColor() const
463 {
464     GtkWidget* widget = gtkTreeView();
465     return makeColor(widget->style->base[GTK_STATE_SELECTED]);
466 }
467
468 Color RenderThemeChromiumGtk::activeListBoxSelectionForegroundColor() const
469 {
470     GtkWidget* widget = gtkTreeView();
471     return makeColor(widget->style->text[GTK_STATE_SELECTED]);
472 }
473
474 Color RenderThemeChromiumGtk::inactiveListBoxSelectionBackgroundColor() const
475 {
476     GtkWidget* widget = gtkTreeView();
477     return makeColor(widget->style->base[GTK_STATE_ACTIVE]);
478 }
479
480 Color RenderThemeChromiumGtk::inactiveListBoxSelectionForegroundColor() const
481 {
482     GtkWidget* widget = gtkTreeView();
483     return makeColor(widget->style->text[GTK_STATE_ACTIVE]);
484 }
485
486 GtkWidget* RenderThemeChromiumGtk::gtkEntry() const
487 {
488     if (m_gtkEntry)
489         return m_gtkEntry;
490
491     m_gtkEntry = gtk_entry_new();
492     g_signal_connect(m_gtkEntry, "style-set", G_CALLBACK(gtkStyleSetCallback), theme());
493     gtk_container_add(gtkContainer(), m_gtkEntry);
494     gtk_widget_realize(m_gtkEntry);
495
496     return m_gtkEntry;
497 }
498
499 GtkWidget* RenderThemeChromiumGtk::gtkTreeView() const
500 {
501     if (m_gtkTreeView)
502         return m_gtkTreeView;
503
504     m_gtkTreeView = gtk_tree_view_new();
505     g_signal_connect(m_gtkTreeView, "style-set", G_CALLBACK(gtkStyleSetCallback), theme());
506     gtk_container_add(gtkContainer(), m_gtkTreeView);
507     gtk_widget_realize(m_gtkTreeView);
508
509     return m_gtkTreeView;
510 }
511
512 GtkContainer* RenderThemeChromiumGtk::gtkContainer() const
513 {
514     if (m_gtkContainer)
515         return m_gtkContainer;
516
517     m_gtkWindow = gtk_window_new(GTK_WINDOW_POPUP);
518     m_gtkContainer = GTK_CONTAINER(gtk_fixed_new());
519     gtk_container_add(GTK_CONTAINER(m_gtkWindow), GTK_WIDGET(m_gtkContainer));
520     gtk_widget_realize(m_gtkWindow);
521
522     return m_gtkContainer;
523 }
524
525 int RenderThemeChromiumGtk::menuListInternalPadding(RenderStyle* style, int paddingType) const
526 {
527     // This internal padding is in addition to the user-supplied padding.
528     // Matches the FF behavior.
529     int padding = styledMenuListInternalPadding[paddingType];
530
531     // Reserve the space for right arrow here. The rest of the padding is
532     // set by adjustMenuListStyle, since PopMenuWin.cpp uses the padding from
533     // RenderMenuList to lay out the individual items in the popup.
534     // If the MenuList actually has appearance "NoAppearance", then that means
535     // we don't draw a button, so don't reserve space for it.
536     const int bar_type = style->direction() == LTR ? RightPadding : LeftPadding;
537     if (paddingType == bar_type && style->appearance() != NoControlPart)
538         padding += ScrollbarTheme::nativeTheme()->scrollbarThickness();
539
540     return padding;
541 }
542
543 } // namespace WebCore