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 "core/rendering/RenderThemeChromiumSkia.h"
26
27#include "UserAgentStyleSheets.h"
28#include "core/platform/LayoutTestSupport.h"
29#include "core/platform/ScrollbarTheme.h"
30#include "core/platform/graphics/GraphicsContext.h"
31#include "core/platform/graphics/Image.h"
32#include "core/rendering/PaintInfo.h"
33#include "core/rendering/RenderBox.h"
34#include "core/rendering/RenderMediaControlsChromium.h"
35#include "core/rendering/RenderObject.h"
36#include "core/rendering/RenderProgress.h"
37#include "core/rendering/RenderThemeChromiumFontProvider.h"
38#include "wtf/CurrentTime.h"
39
40namespace WebCore {
41
42enum PaddingType {
43    TopPadding,
44    RightPadding,
45    BottomPadding,
46    LeftPadding
47};
48
49static const int styledMenuListInternalPadding[4] = { 1, 4, 1, 4 };
50
51// These values all match Safari/Win.
52static const float defaultControlFontPixelSize = 13;
53static const float defaultCancelButtonSize = 9;
54static const float minCancelButtonSize = 5;
55static const float maxCancelButtonSize = 21;
56static const float defaultSearchFieldResultsDecorationSize = 13;
57static const float minSearchFieldResultsDecorationSize = 9;
58static const float maxSearchFieldResultsDecorationSize = 30;
59
60RenderThemeChromiumSkia::RenderThemeChromiumSkia()
61{
62}
63
64RenderThemeChromiumSkia::~RenderThemeChromiumSkia()
65{
66}
67
68// Use the Windows style sheets to match their metrics.
69String RenderThemeChromiumSkia::extraDefaultStyleSheet()
70{
71    return RenderTheme::extraDefaultStyleSheet() +
72        String(themeWinUserAgentStyleSheet, sizeof(themeWinUserAgentStyleSheet)) +
73        String(themeChromiumSkiaUserAgentStyleSheet, sizeof(themeChromiumSkiaUserAgentStyleSheet)) +
74        String(themeChromiumUserAgentStyleSheet, sizeof(themeChromiumUserAgentStyleSheet));
75}
76
77String RenderThemeChromiumSkia::extraQuirksStyleSheet()
78{
79    return String(themeWinQuirksUserAgentStyleSheet, sizeof(themeWinQuirksUserAgentStyleSheet));
80}
81
82bool RenderThemeChromiumSkia::supportsHover(const RenderStyle* style) const
83{
84    return true;
85}
86
87bool RenderThemeChromiumSkia::supportsFocusRing(const RenderStyle* style) const
88{
89    // This causes WebKit to draw the focus rings for us.
90    return false;
91}
92
93bool RenderThemeChromiumSkia::supportsClosedCaptioning() const
94{
95    return true;
96}
97
98Color RenderThemeChromiumSkia::platformActiveSelectionBackgroundColor() const
99{
100    return Color(0x1e, 0x90, 0xff);
101}
102
103Color RenderThemeChromiumSkia::platformInactiveSelectionBackgroundColor() const
104{
105    return Color(0xc8, 0xc8, 0xc8);
106}
107
108Color RenderThemeChromiumSkia::platformActiveSelectionForegroundColor() const
109{
110    return Color::black;
111}
112
113Color RenderThemeChromiumSkia::platformInactiveSelectionForegroundColor() const
114{
115    return Color(0x32, 0x32, 0x32);
116}
117
118Color RenderThemeChromiumSkia::platformFocusRingColor() const
119{
120    static Color focusRingColor(229, 151, 0, 255);
121    return focusRingColor;
122}
123
124double RenderThemeChromiumSkia::caretBlinkInterval() const
125{
126    // Disable the blinking caret in layout test mode, as it introduces
127    // a race condition for the pixel tests. http://b/1198440
128    if (isRunningLayoutTest())
129        return 0;
130
131    return caretBlinkIntervalInternal();
132}
133
134void RenderThemeChromiumSkia::systemFont(CSSValueID valueID, FontDescription& fontDescription) const
135{
136    RenderThemeChromiumFontProvider::systemFont(valueID, fontDescription);
137}
138
139int RenderThemeChromiumSkia::minimumMenuListSize(RenderStyle* style) const
140{
141    return 0;
142}
143
144// These are the default dimensions of radio buttons and checkboxes.
145static const int widgetStandardWidth = 13;
146static const int widgetStandardHeight = 13;
147
148// Return a rectangle that has the same center point as |original|, but with a
149// size capped at |width| by |height|.
150IntRect center(const IntRect& original, int width, int height)
151{
152    width = std::min(original.width(), width);
153    height = std::min(original.height(), height);
154    int x = original.x() + (original.width() - width) / 2;
155    int y = original.y() + (original.height() - height) / 2;
156
157    return IntRect(x, y, width, height);
158}
159
160void RenderThemeChromiumSkia::setCheckboxSize(RenderStyle* style) const
161{
162    // If the width and height are both specified, then we have nothing to do.
163    if (!style->width().isIntrinsicOrAuto() && !style->height().isAuto())
164        return;
165
166    // FIXME:  A hard-coded size of 13 is used.  This is wrong but necessary
167    // for now.  It matches Firefox.  At different DPI settings on Windows,
168    // querying the theme gives you a larger size that accounts for the higher
169    // DPI.  Until our entire engine honors a DPI setting other than 96, we
170    // can't rely on the theme's metrics.
171    const IntSize size(widgetStandardWidth, widgetStandardHeight);
172    setSizeIfAuto(style, size);
173}
174
175void RenderThemeChromiumSkia::setRadioSize(RenderStyle* style) const
176{
177    // Use same sizing for radio box as checkbox.
178    setCheckboxSize(style);
179}
180
181void RenderThemeChromiumSkia::adjustButtonStyle(RenderStyle* style, Element*) const
182{
183    if (style->appearance() == PushButtonPart) {
184        // Ignore line-height.
185        style->setLineHeight(RenderStyle::initialLineHeight());
186    }
187}
188
189bool RenderThemeChromiumSkia::paintTextArea(RenderObject* o, const PaintInfo& i, const IntRect& r)
190{
191    return paintTextField(o, i, r);
192}
193
194void RenderThemeChromiumSkia::adjustSearchFieldStyle(RenderStyle* style, Element*) const
195{
196     // Ignore line-height.
197     style->setLineHeight(RenderStyle::initialLineHeight());
198}
199
200bool RenderThemeChromiumSkia::paintSearchField(RenderObject* o, const PaintInfo& i, const IntRect& r)
201{
202    return paintTextField(o, i, r);
203}
204
205void RenderThemeChromiumSkia::adjustSearchFieldCancelButtonStyle(RenderStyle* style, Element*) const
206{
207    // Scale the button size based on the font size
208    float fontScale = style->fontSize() / defaultControlFontPixelSize;
209    int cancelButtonSize = lroundf(std::min(std::max(minCancelButtonSize, defaultCancelButtonSize * fontScale), maxCancelButtonSize));
210    style->setWidth(Length(cancelButtonSize, Fixed));
211    style->setHeight(Length(cancelButtonSize, Fixed));
212}
213
214IntRect RenderThemeChromiumSkia::convertToPaintingRect(RenderObject* inputRenderer, const RenderObject* partRenderer, LayoutRect partRect, const IntRect& localOffset) const
215{
216    // Compute an offset between the part renderer and the input renderer.
217    LayoutSize offsetFromInputRenderer = -partRenderer->offsetFromAncestorContainer(inputRenderer);
218    // Move the rect into partRenderer's coords.
219    partRect.move(offsetFromInputRenderer);
220    // Account for the local drawing offset.
221    partRect.move(localOffset.x(), localOffset.y());
222
223    return pixelSnappedIntRect(partRect);
224}
225
226bool RenderThemeChromiumSkia::paintSearchFieldCancelButton(RenderObject* cancelButtonObject, const PaintInfo& paintInfo, const IntRect& r)
227{
228    // Get the renderer of <input> element.
229    Node* input = cancelButtonObject->node()->shadowHost();
230    RenderObject* baseRenderer = input ? input->renderer() : cancelButtonObject;
231    if (!baseRenderer->isBox())
232        return false;
233    RenderBox* inputRenderBox = toRenderBox(baseRenderer);
234    LayoutRect inputContentBox = inputRenderBox->contentBoxRect();
235
236    // Make sure the scaled button stays square and will fit in its parent's box.
237    LayoutUnit cancelButtonSize = std::min(inputContentBox.width(), std::min<LayoutUnit>(inputContentBox.height(), r.height()));
238    // Calculate cancel button's coordinates relative to the input element.
239    // Center the button vertically.  Round up though, so if it has to be one pixel off-center, it will
240    // be one pixel closer to the bottom of the field.  This tends to look better with the text.
241    LayoutRect cancelButtonRect(cancelButtonObject->offsetFromAncestorContainer(inputRenderBox).width(),
242                                inputContentBox.y() + (inputContentBox.height() - cancelButtonSize + 1) / 2,
243                                cancelButtonSize, cancelButtonSize);
244    IntRect paintingRect = convertToPaintingRect(inputRenderBox, cancelButtonObject, cancelButtonRect, r);
245
246    static Image* cancelImage = Image::loadPlatformResource("searchCancel").leakRef();
247    static Image* cancelPressedImage = Image::loadPlatformResource("searchCancelPressed").leakRef();
248    paintInfo.context->drawImage(isPressed(cancelButtonObject) ? cancelPressedImage : cancelImage, paintingRect);
249    return false;
250}
251
252void RenderThemeChromiumSkia::adjustSearchFieldDecorationStyle(RenderStyle* style, Element*) const
253{
254    IntSize emptySize(1, 11);
255    style->setWidth(Length(emptySize.width(), Fixed));
256    style->setHeight(Length(emptySize.height(), Fixed));
257}
258
259void RenderThemeChromiumSkia::adjustSearchFieldResultsDecorationStyle(RenderStyle* style, Element*) const
260{
261    // Scale the decoration size based on the font size
262    float fontScale = style->fontSize() / defaultControlFontPixelSize;
263    int magnifierSize = lroundf(std::min(std::max(minSearchFieldResultsDecorationSize, defaultSearchFieldResultsDecorationSize * fontScale),
264                                         maxSearchFieldResultsDecorationSize));
265    style->setWidth(Length(magnifierSize, Fixed));
266    style->setHeight(Length(magnifierSize, Fixed));
267}
268
269bool RenderThemeChromiumSkia::paintSearchFieldResultsDecoration(RenderObject* magnifierObject, const PaintInfo& paintInfo, const IntRect& r)
270{
271    // Get the renderer of <input> element.
272    Node* input = magnifierObject->node()->shadowHost();
273    RenderObject* baseRenderer = input ? input->renderer() : magnifierObject;
274    if (!baseRenderer->isBox())
275        return false;
276    RenderBox* inputRenderBox = toRenderBox(baseRenderer);
277    LayoutRect inputContentBox = inputRenderBox->contentBoxRect();
278
279    // Make sure the scaled decoration stays square and will fit in its parent's box.
280    LayoutUnit magnifierSize = std::min(inputContentBox.width(), std::min<LayoutUnit>(inputContentBox.height(), r.height()));
281    // Calculate decoration's coordinates relative to the input element.
282    // Center the decoration vertically.  Round up though, so if it has to be one pixel off-center, it will
283    // be one pixel closer to the bottom of the field.  This tends to look better with the text.
284    LayoutRect magnifierRect(magnifierObject->offsetFromAncestorContainer(inputRenderBox).width(),
285                             inputContentBox.y() + (inputContentBox.height() - magnifierSize + 1) / 2,
286                             magnifierSize, magnifierSize);
287    IntRect paintingRect = convertToPaintingRect(inputRenderBox, magnifierObject, magnifierRect, r);
288
289    static Image* magnifierImage = Image::loadPlatformResource("searchMagnifier").leakRef();
290    paintInfo.context->drawImage(magnifierImage, paintingRect);
291    return false;
292}
293
294bool RenderThemeChromiumSkia::paintMediaSliderTrack(RenderObject* object, const PaintInfo& paintInfo, const IntRect& rect)
295{
296    return RenderMediaControlsChromium::paintMediaControlsPart(MediaSlider, object, paintInfo, rect);
297}
298
299bool RenderThemeChromiumSkia::paintMediaVolumeSliderTrack(RenderObject* object, const PaintInfo& paintInfo, const IntRect& rect)
300{
301    return RenderMediaControlsChromium::paintMediaControlsPart(MediaVolumeSlider, object, paintInfo, rect);
302}
303
304void RenderThemeChromiumSkia::adjustSliderThumbSize(RenderStyle* style, Element*) const
305{
306    RenderMediaControlsChromium::adjustMediaSliderThumbSize(style);
307}
308
309bool RenderThemeChromiumSkia::paintMediaSliderThumb(RenderObject* object, const PaintInfo& paintInfo, const IntRect& rect)
310{
311    return RenderMediaControlsChromium::paintMediaControlsPart(MediaSliderThumb, object, paintInfo, rect);
312}
313
314bool RenderThemeChromiumSkia::paintMediaToggleClosedCaptionsButton(RenderObject* o, const PaintInfo& paintInfo, const IntRect& r)
315{
316    return RenderMediaControlsChromium::paintMediaControlsPart(MediaShowClosedCaptionsButton, o, paintInfo, r);
317}
318
319bool RenderThemeChromiumSkia::paintMediaVolumeSliderThumb(RenderObject* object, const PaintInfo& paintInfo, const IntRect& rect)
320{
321    return RenderMediaControlsChromium::paintMediaControlsPart(MediaVolumeSliderThumb, object, paintInfo, rect);
322}
323
324bool RenderThemeChromiumSkia::paintMediaPlayButton(RenderObject* object, const PaintInfo& paintInfo, const IntRect& rect)
325{
326    return RenderMediaControlsChromium::paintMediaControlsPart(MediaPlayButton, object, paintInfo, rect);
327}
328
329bool RenderThemeChromiumSkia::paintMediaMuteButton(RenderObject* object, const PaintInfo& paintInfo, const IntRect& rect)
330{
331    return RenderMediaControlsChromium::paintMediaControlsPart(MediaMuteButton, object, paintInfo, rect);
332}
333
334String RenderThemeChromiumSkia::formatMediaControlsTime(float time) const
335{
336    return RenderMediaControlsChromium::formatMediaControlsTime(time);
337}
338
339String RenderThemeChromiumSkia::formatMediaControlsCurrentTime(float currentTime, float duration) const
340{
341    return RenderMediaControlsChromium::formatMediaControlsCurrentTime(currentTime, duration);
342}
343
344bool RenderThemeChromiumSkia::paintMediaFullscreenButton(RenderObject* object, const PaintInfo& paintInfo, const IntRect& rect)
345{
346    return RenderMediaControlsChromium::paintMediaControlsPart(MediaEnterFullscreenButton, object, paintInfo, rect);
347}
348
349void RenderThemeChromiumSkia::adjustMenuListStyle(RenderStyle* style, WebCore::Element*) const
350{
351    // Height is locked to auto on all browsers.
352    style->setLineHeight(RenderStyle::initialLineHeight());
353}
354
355void RenderThemeChromiumSkia::adjustMenuListButtonStyle(RenderStyle* style, Element* e) const
356{
357    adjustMenuListStyle(style, e);
358}
359
360// Used to paint styled menulists (i.e. with a non-default border)
361bool RenderThemeChromiumSkia::paintMenuListButton(RenderObject* o, const PaintInfo& i, const IntRect& rect)
362{
363    return paintMenuList(o, i, rect);
364}
365
366int RenderThemeChromiumSkia::popupInternalPaddingLeft(RenderStyle* style) const
367{
368    return menuListInternalPadding(style, LeftPadding);
369}
370
371int RenderThemeChromiumSkia::popupInternalPaddingRight(RenderStyle* style) const
372{
373    return menuListInternalPadding(style, RightPadding);
374}
375
376int RenderThemeChromiumSkia::popupInternalPaddingTop(RenderStyle* style) const
377{
378    return menuListInternalPadding(style, TopPadding);
379}
380
381int RenderThemeChromiumSkia::popupInternalPaddingBottom(RenderStyle* style) const
382{
383    return menuListInternalPadding(style, BottomPadding);
384}
385
386// static
387void RenderThemeChromiumSkia::setDefaultFontSize(int fontSize)
388{
389    RenderThemeChromiumFontProvider::setDefaultFontSize(fontSize);
390}
391
392double RenderThemeChromiumSkia::caretBlinkIntervalInternal() const
393{
394    return RenderTheme::caretBlinkInterval();
395}
396
397int RenderThemeChromiumSkia::menuListArrowPadding() const
398{
399    return ScrollbarTheme::theme()->scrollbarThickness();
400}
401
402int RenderThemeChromiumSkia::menuListInternalPadding(RenderStyle* style, int paddingType) const
403{
404    // This internal padding is in addition to the user-supplied padding.
405    // Matches the FF behavior.
406    int padding = styledMenuListInternalPadding[paddingType];
407
408    // Reserve the space for right arrow here. The rest of the padding is
409    // set by adjustMenuListStyle, since PopMenuWin.cpp uses the padding from
410    // RenderMenuList to lay out the individual items in the popup.
411    // If the MenuList actually has appearance "NoAppearance", then that means
412    // we don't draw a button, so don't reserve space for it.
413    const int barType = style->direction() == LTR ? RightPadding : LeftPadding;
414    if (paddingType == barType && style->appearance() != NoControlPart)
415        padding += menuListArrowPadding();
416
417    return padding;
418}
419
420bool RenderThemeChromiumSkia::shouldShowPlaceholderWhenFocused() const
421{
422    return true;
423}
424
425//
426// Following values are come from default of GTK+
427//
428static const int progressDeltaPixelsPerSecond = 100;
429static const int progressActivityBlocks = 5;
430static const int progressAnimationFrmaes = 10;
431static const double progressAnimationInterval = 0.125;
432
433IntRect RenderThemeChromiumSkia::determinateProgressValueRectFor(RenderProgress* renderProgress, const IntRect& rect) const
434{
435    int dx = rect.width() * renderProgress->position();
436    return IntRect(rect.x(), rect.y(), dx, rect.height());
437}
438
439IntRect RenderThemeChromiumSkia::indeterminateProgressValueRectFor(RenderProgress* renderProgress, const IntRect& rect) const
440{
441
442    int valueWidth = rect.width() / progressActivityBlocks;
443    int movableWidth = rect.width() - valueWidth;
444    if (movableWidth <= 0)
445        return IntRect();
446
447    double progress = renderProgress->animationProgress();
448    if (progress < 0.5)
449        return IntRect(rect.x() + progress * 2 * movableWidth, rect.y(), valueWidth, rect.height());
450    return IntRect(rect.x() + (1.0 - progress) * 2 * movableWidth, rect.y(), valueWidth, rect.height());
451}
452
453double RenderThemeChromiumSkia::animationRepeatIntervalForProgressBar(RenderProgress*) const
454{
455    return progressAnimationInterval;
456}
457
458double RenderThemeChromiumSkia::animationDurationForProgressBar(RenderProgress* renderProgress) const
459{
460    return progressAnimationInterval * progressAnimationFrmaes * 2; // "2" for back and forth
461}
462
463IntRect RenderThemeChromiumSkia::progressValueRectFor(RenderProgress* renderProgress, const IntRect& rect) const
464{
465    return renderProgress->isDeterminate() ? determinateProgressValueRectFor(renderProgress, rect) : indeterminateProgressValueRectFor(renderProgress, rect);
466}
467
468RenderThemeChromiumSkia::DirectionFlippingScope::DirectionFlippingScope(RenderObject* renderer, const PaintInfo& paintInfo, const IntRect& rect)
469    : m_needsFlipping(!renderer->style()->isLeftToRightDirection())
470    , m_paintInfo(paintInfo)
471{
472    if (!m_needsFlipping)
473        return;
474    m_paintInfo.context->save();
475    m_paintInfo.context->translate(2 * rect.x() + rect.width(), 0);
476    m_paintInfo.context->scale(FloatSize(-1, 1));
477}
478
479RenderThemeChromiumSkia::DirectionFlippingScope::~DirectionFlippingScope()
480{
481    if (!m_needsFlipping)
482        return;
483    m_paintInfo.context->restore();
484}
485
486} // namespace WebCore
487