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