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 * Copyright (C) 2009 Kenneth Rohde Christiansen
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 "core/rendering/RenderThemeChromiumDefault.h"
27
28#include "core/CSSValueKeywords.h"
29#include "core/UserAgentStyleSheets.h"
30#include "core/rendering/PaintInfo.h"
31#include "core/rendering/RenderObject.h"
32#include "core/rendering/RenderProgress.h"
33#include "platform/LayoutTestSupport.h"
34#include "platform/graphics/Color.h"
35#include "platform/graphics/GraphicsContext.h"
36#include "platform/graphics/GraphicsContextStateSaver.h"
37#include "public/platform/Platform.h"
38#include "public/platform/WebRect.h"
39#include "public/platform/WebThemeEngine.h"
40#include "wtf/StdLibExtras.h"
41
42namespace blink {
43
44static bool useMockTheme()
45{
46    return LayoutTestSupport::isRunningLayoutTest();
47}
48
49unsigned RenderThemeChromiumDefault::m_activeSelectionBackgroundColor =
50    0xff1e90ff;
51unsigned RenderThemeChromiumDefault::m_activeSelectionForegroundColor =
52    Color::black;
53unsigned RenderThemeChromiumDefault::m_inactiveSelectionBackgroundColor =
54    0xffc8c8c8;
55unsigned RenderThemeChromiumDefault::m_inactiveSelectionForegroundColor =
56    0xff323232;
57
58double RenderThemeChromiumDefault::m_caretBlinkInterval;
59
60static const unsigned defaultButtonBackgroundColor = 0xffdddddd;
61
62static WebThemeEngine::State getWebThemeState(const RenderTheme* theme, const RenderObject* o)
63{
64    if (!theme->isEnabled(o))
65        return WebThemeEngine::StateDisabled;
66    if (useMockTheme() && theme->isReadOnlyControl(o))
67        return WebThemeEngine::StateReadonly;
68    if (theme->isPressed(o))
69        return WebThemeEngine::StatePressed;
70    if (useMockTheme() && theme->isFocused(o))
71        return WebThemeEngine::StateFocused;
72    if (theme->isHovered(o))
73        return WebThemeEngine::StateHover;
74
75    return WebThemeEngine::StateNormal;
76}
77
78PassRefPtr<RenderTheme> RenderThemeChromiumDefault::create()
79{
80    return adoptRef(new RenderThemeChromiumDefault());
81}
82
83// RenderTheme::theme for Android is defined in RenderThemeChromiumAndroid.cpp.
84#if !OS(ANDROID)
85RenderTheme& RenderTheme::theme()
86{
87    DEFINE_STATIC_REF(RenderTheme, renderTheme, (RenderThemeChromiumDefault::create()));
88    return *renderTheme;
89}
90#endif
91
92RenderThemeChromiumDefault::RenderThemeChromiumDefault()
93{
94    m_caretBlinkInterval = RenderTheme::caretBlinkInterval();
95}
96
97RenderThemeChromiumDefault::~RenderThemeChromiumDefault()
98{
99}
100
101bool RenderThemeChromiumDefault::supportsFocusRing(const RenderStyle* style) const
102{
103    if (useMockTheme()) {
104        // Don't use focus rings for buttons when mocking controls.
105        return style->appearance() == ButtonPart
106            || style->appearance() == PushButtonPart
107            || style->appearance() == SquareButtonPart;
108    }
109
110    return RenderThemeChromiumSkia::supportsFocusRing(style);
111}
112
113Color RenderThemeChromiumDefault::systemColor(CSSValueID cssValueId) const
114{
115    static const Color defaultButtonGrayColor(0xffdddddd);
116    static const Color defaultMenuColor(0xfff7f7f7);
117
118    if (cssValueId == CSSValueButtonface) {
119        if (useMockTheme())
120            return Color(0xc0, 0xc0, 0xc0);
121        return defaultButtonGrayColor;
122    }
123    if (cssValueId == CSSValueMenu)
124        return defaultMenuColor;
125    return RenderTheme::systemColor(cssValueId);
126}
127
128String RenderThemeChromiumDefault::extraDefaultStyleSheet()
129{
130    // FIXME: We should not have OS() branches here.
131    // We should have something like RenderThemeWin, RenderThemeLinux, or
132    // should concatenate UA stylesheets on build time.
133#if !OS(WIN)
134    return RenderThemeChromiumSkia::extraDefaultStyleSheet() +
135#if !OS(ANDROID)
136        String(themeInputMultipleFieldsCss, sizeof(themeInputMultipleFieldsCss)) +
137#endif
138        String(themeChromiumLinuxCss, sizeof(themeChromiumLinuxCss));
139#else
140    return RenderThemeChromiumSkia::extraDefaultStyleSheet() +
141        String(themeInputMultipleFieldsCss, sizeof(themeInputMultipleFieldsCss));
142#endif
143}
144
145Color RenderThemeChromiumDefault::activeListBoxSelectionBackgroundColor() const
146{
147    return Color(0x28, 0x28, 0x28);
148}
149
150Color RenderThemeChromiumDefault::activeListBoxSelectionForegroundColor() const
151{
152    return Color::black;
153}
154
155Color RenderThemeChromiumDefault::inactiveListBoxSelectionBackgroundColor() const
156{
157    return Color(0xc8, 0xc8, 0xc8);
158}
159
160Color RenderThemeChromiumDefault::inactiveListBoxSelectionForegroundColor() const
161{
162    return Color(0x32, 0x32, 0x32);
163}
164
165Color RenderThemeChromiumDefault::platformActiveSelectionBackgroundColor() const
166{
167    if (useMockTheme())
168        return Color(0x00, 0x00, 0xff); // Royal blue.
169    return m_activeSelectionBackgroundColor;
170}
171
172Color RenderThemeChromiumDefault::platformInactiveSelectionBackgroundColor() const
173{
174    if (useMockTheme())
175        return Color(0x99, 0x99, 0x99); // Medium gray.
176    return m_inactiveSelectionBackgroundColor;
177}
178
179Color RenderThemeChromiumDefault::platformActiveSelectionForegroundColor() const
180{
181    if (useMockTheme())
182        return Color(0xff, 0xff, 0xcc); // Pale yellow.
183    return m_activeSelectionForegroundColor;
184}
185
186Color RenderThemeChromiumDefault::platformInactiveSelectionForegroundColor() const
187{
188    if (useMockTheme())
189        return Color::white;
190    return m_inactiveSelectionForegroundColor;
191}
192
193IntSize RenderThemeChromiumDefault::sliderTickSize() const
194{
195    if (useMockTheme())
196        return IntSize(1, 3);
197    return IntSize(1, 6);
198}
199
200int RenderThemeChromiumDefault::sliderTickOffsetFromTrackCenter() const
201{
202    if (useMockTheme())
203        return 11;
204    return -16;
205}
206
207void RenderThemeChromiumDefault::adjustSliderThumbSize(RenderStyle* style, Element* element) const
208{
209    IntSize size = Platform::current()->themeEngine()->getSize(WebThemeEngine::PartSliderThumb);
210
211    // FIXME: Mock theme doesn't handle zoomed sliders.
212    float zoomLevel = useMockTheme() ? 1 : style->effectiveZoom();
213    if (style->appearance() == SliderThumbHorizontalPart) {
214        style->setWidth(Length(size.width() * zoomLevel, Fixed));
215        style->setHeight(Length(size.height() * zoomLevel, Fixed));
216    } else if (style->appearance() == SliderThumbVerticalPart) {
217        style->setWidth(Length(size.height() * zoomLevel, Fixed));
218        style->setHeight(Length(size.width() * zoomLevel, Fixed));
219    } else
220        RenderThemeChromiumSkia::adjustSliderThumbSize(style, element);
221}
222
223void RenderThemeChromiumDefault::setCaretBlinkInterval(double interval)
224{
225    m_caretBlinkInterval = interval;
226}
227
228double RenderThemeChromiumDefault::caretBlinkIntervalInternal() const
229{
230    return m_caretBlinkInterval;
231}
232
233void RenderThemeChromiumDefault::setSelectionColors(
234    unsigned activeBackgroundColor,
235    unsigned activeForegroundColor,
236    unsigned inactiveBackgroundColor,
237    unsigned inactiveForegroundColor)
238{
239    m_activeSelectionBackgroundColor = activeBackgroundColor;
240    m_activeSelectionForegroundColor = activeForegroundColor;
241    m_inactiveSelectionBackgroundColor = inactiveBackgroundColor;
242    m_inactiveSelectionForegroundColor = inactiveForegroundColor;
243}
244
245bool RenderThemeChromiumDefault::paintCheckbox(RenderObject* o, const PaintInfo& i, const IntRect& rect)
246{
247    WebThemeEngine::ExtraParams extraParams;
248    WebCanvas* canvas = i.context->canvas();
249    extraParams.button.checked = isChecked(o);
250    extraParams.button.indeterminate = isIndeterminate(o);
251
252    float zoomLevel = o->style()->effectiveZoom();
253    GraphicsContextStateSaver stateSaver(*i.context, false);
254    IntRect unzoomedRect = rect;
255    if (zoomLevel != 1) {
256        stateSaver.save();
257        unzoomedRect.setWidth(unzoomedRect.width() / zoomLevel);
258        unzoomedRect.setHeight(unzoomedRect.height() / zoomLevel);
259        i.context->translate(unzoomedRect.x(), unzoomedRect.y());
260        i.context->scale(zoomLevel, zoomLevel);
261        i.context->translate(-unzoomedRect.x(), -unzoomedRect.y());
262    }
263
264    Platform::current()->themeEngine()->paint(canvas, WebThemeEngine::PartCheckbox, getWebThemeState(this, o), WebRect(unzoomedRect), &extraParams);
265    return false;
266}
267
268void RenderThemeChromiumDefault::setCheckboxSize(RenderStyle* style) const
269{
270    // If the width and height are both specified, then we have nothing to do.
271    if (!style->width().isIntrinsicOrAuto() && !style->height().isAuto())
272        return;
273
274    IntSize size = Platform::current()->themeEngine()->getSize(WebThemeEngine::PartCheckbox);
275    float zoomLevel = style->effectiveZoom();
276    size.setWidth(size.width() * zoomLevel);
277    size.setHeight(size.height() * zoomLevel);
278    setSizeIfAuto(style, size);
279}
280
281bool RenderThemeChromiumDefault::paintRadio(RenderObject* o, const PaintInfo& i, const IntRect& rect)
282{
283    WebThemeEngine::ExtraParams extraParams;
284    WebCanvas* canvas = i.context->canvas();
285    extraParams.button.checked = isChecked(o);
286
287    Platform::current()->themeEngine()->paint(canvas, WebThemeEngine::PartRadio, getWebThemeState(this, o), WebRect(rect), &extraParams);
288    return false;
289}
290
291void RenderThemeChromiumDefault::setRadioSize(RenderStyle* style) const
292{
293    // If the width and height are both specified, then we have nothing to do.
294    if (!style->width().isIntrinsicOrAuto() && !style->height().isAuto())
295        return;
296
297    IntSize size = Platform::current()->themeEngine()->getSize(WebThemeEngine::PartRadio);
298    float zoomLevel = style->effectiveZoom();
299    size.setWidth(size.width() * zoomLevel);
300    size.setHeight(size.height() * zoomLevel);
301    setSizeIfAuto(style, size);
302}
303
304bool RenderThemeChromiumDefault::paintButton(RenderObject* o, const PaintInfo& i, const IntRect& rect)
305{
306    WebThemeEngine::ExtraParams extraParams;
307    WebCanvas* canvas = i.context->canvas();
308    extraParams.button.hasBorder = true;
309    extraParams.button.backgroundColor = useMockTheme() ? 0xffc0c0c0 : defaultButtonBackgroundColor;
310    if (o->hasBackground())
311        extraParams.button.backgroundColor = o->resolveColor(CSSPropertyBackgroundColor).rgb();
312
313    Platform::current()->themeEngine()->paint(canvas, WebThemeEngine::PartButton, getWebThemeState(this, o), WebRect(rect), &extraParams);
314    return false;
315}
316
317bool RenderThemeChromiumDefault::paintTextField(RenderObject* o, const PaintInfo& i, const IntRect& rect)
318{
319    // WebThemeEngine does not handle border rounded corner and background image
320    // so return true to draw CSS border and background.
321    if (o->style()->hasBorderRadius() || o->style()->hasBackgroundImage())
322        return true;
323
324    ControlPart part = o->style()->appearance();
325
326    WebThemeEngine::ExtraParams extraParams;
327    extraParams.textField.isTextArea = part == TextAreaPart;
328    extraParams.textField.isListbox = part == ListboxPart;
329
330    WebCanvas* canvas = i.context->canvas();
331
332    Color backgroundColor = o->resolveColor(CSSPropertyBackgroundColor);
333    extraParams.textField.backgroundColor = backgroundColor.rgb();
334
335    Platform::current()->themeEngine()->paint(canvas, WebThemeEngine::PartTextField, getWebThemeState(this, o), WebRect(rect), &extraParams);
336    return false;
337}
338
339bool RenderThemeChromiumDefault::paintMenuList(RenderObject* o, const PaintInfo& i, const IntRect& rect)
340{
341    if (!o->isBox())
342        return false;
343
344    const int right = rect.x() + rect.width();
345    const int middle = rect.y() + rect.height() / 2;
346
347    WebThemeEngine::ExtraParams extraParams;
348    extraParams.menuList.arrowY = middle;
349    const RenderBox* box = toRenderBox(o);
350    // Match Chromium Win behaviour of showing all borders if any are shown.
351    extraParams.menuList.hasBorder = box->borderRight() || box->borderLeft() || box->borderTop() || box->borderBottom();
352    extraParams.menuList.hasBorderRadius = o->style()->hasBorderRadius();
353    // Fallback to transparent if the specified color object is invalid.
354    Color backgroundColor(Color::transparent);
355    if (o->hasBackground())
356        backgroundColor = o->resolveColor(CSSPropertyBackgroundColor);
357    extraParams.menuList.backgroundColor = backgroundColor.rgb();
358
359    // If we have a background image, don't fill the content area to expose the
360    // parent's background. Also, we shouldn't fill the content area if the
361    // alpha of the color is 0. The API of Windows GDI ignores the alpha.
362    // FIXME: the normal Aura theme doesn't care about this, so we should
363    // investigate if we really need fillContentArea.
364    extraParams.menuList.fillContentArea = !o->style()->hasBackgroundImage() && backgroundColor.alpha();
365
366    if (useMockTheme()) {
367        // The size and position of the drop-down button is different between
368        // the mock theme and the regular aura theme.
369        int spacingTop = box->borderTop() + box->paddingTop();
370        int spacingBottom = box->borderBottom() + box->paddingBottom();
371        int spacingRight = box->borderRight() + box->paddingRight();
372        extraParams.menuList.arrowX = (o->style()->direction() == RTL) ? rect.x() + 4 + spacingRight: right - 13 - spacingRight;
373        extraParams.menuList.arrowHeight = rect.height() - spacingBottom - spacingTop;
374    } else {
375        extraParams.menuList.arrowX = (o->style()->direction() == RTL) ? rect.x() + 7 : right - 13;
376    }
377
378    WebCanvas* canvas = i.context->canvas();
379
380    Platform::current()->themeEngine()->paint(canvas, WebThemeEngine::PartMenuList, getWebThemeState(this, o), WebRect(rect), &extraParams);
381    return false;
382}
383
384bool RenderThemeChromiumDefault::paintMenuListButton(RenderObject* o, const PaintInfo& i, const IntRect& rect)
385{
386    if (!o->isBox())
387        return false;
388
389    const int right = rect.x() + rect.width();
390    const int middle = rect.y() + rect.height() / 2;
391
392    WebThemeEngine::ExtraParams extraParams;
393    extraParams.menuList.arrowY = middle;
394    extraParams.menuList.hasBorder = false;
395    extraParams.menuList.hasBorderRadius = o->style()->hasBorderRadius();
396    extraParams.menuList.backgroundColor = Color::transparent;
397    extraParams.menuList.fillContentArea = false;
398
399    if (useMockTheme()) {
400        const RenderBox* box = toRenderBox(o);
401        // The size and position of the drop-down button is different between
402        // the mock theme and the regular aura theme.
403        int spacingTop = box->borderTop() + box->paddingTop();
404        int spacingBottom = box->borderBottom() + box->paddingBottom();
405        int spacingRight = box->borderRight() + box->paddingRight();
406        extraParams.menuList.arrowX = (o->style()->direction() == RTL) ? rect.x() + 4 + spacingRight: right - 13 - spacingRight;
407        extraParams.menuList.arrowHeight = rect.height() - spacingBottom - spacingTop;
408    } else {
409        extraParams.menuList.arrowX = (o->style()->direction() == RTL) ? rect.x() + 7 : right - 13;
410    }
411
412    WebCanvas* canvas = i.context->canvas();
413
414    Platform::current()->themeEngine()->paint(canvas, WebThemeEngine::PartMenuList, getWebThemeState(this, o), WebRect(rect), &extraParams);
415    return false;
416}
417
418bool RenderThemeChromiumDefault::paintSliderTrack(RenderObject* o, const PaintInfo& i, const IntRect& rect)
419{
420    WebThemeEngine::ExtraParams extraParams;
421    WebCanvas* canvas = i.context->canvas();
422    extraParams.slider.vertical = o->style()->appearance() == SliderVerticalPart;
423
424    paintSliderTicks(o, i, rect);
425
426    // FIXME: Mock theme doesn't handle zoomed sliders.
427    float zoomLevel = useMockTheme() ? 1 : o->style()->effectiveZoom();
428    GraphicsContextStateSaver stateSaver(*i.context, false);
429    IntRect unzoomedRect = rect;
430    if (zoomLevel != 1) {
431        stateSaver.save();
432        unzoomedRect.setWidth(unzoomedRect.width() / zoomLevel);
433        unzoomedRect.setHeight(unzoomedRect.height() / zoomLevel);
434        i.context->translate(unzoomedRect.x(), unzoomedRect.y());
435        i.context->scale(zoomLevel, zoomLevel);
436        i.context->translate(-unzoomedRect.x(), -unzoomedRect.y());
437    }
438
439    Platform::current()->themeEngine()->paint(canvas, WebThemeEngine::PartSliderTrack, getWebThemeState(this, o), WebRect(unzoomedRect), &extraParams);
440
441    return false;
442}
443
444bool RenderThemeChromiumDefault::paintSliderThumb(RenderObject* o, const PaintInfo& i, const IntRect& rect)
445{
446    WebThemeEngine::ExtraParams extraParams;
447    WebCanvas* canvas = i.context->canvas();
448    extraParams.slider.vertical = o->style()->appearance() == SliderThumbVerticalPart;
449    extraParams.slider.inDrag = isPressed(o);
450
451    // FIXME: Mock theme doesn't handle zoomed sliders.
452    float zoomLevel = useMockTheme() ? 1 : o->style()->effectiveZoom();
453    GraphicsContextStateSaver stateSaver(*i.context, false);
454    IntRect unzoomedRect = rect;
455    if (zoomLevel != 1) {
456        stateSaver.save();
457        unzoomedRect.setWidth(unzoomedRect.width() / zoomLevel);
458        unzoomedRect.setHeight(unzoomedRect.height() / zoomLevel);
459        i.context->translate(unzoomedRect.x(), unzoomedRect.y());
460        i.context->scale(zoomLevel, zoomLevel);
461        i.context->translate(-unzoomedRect.x(), -unzoomedRect.y());
462    }
463
464    Platform::current()->themeEngine()->paint(canvas, WebThemeEngine::PartSliderThumb, getWebThemeState(this, o), WebRect(unzoomedRect), &extraParams);
465    return false;
466}
467
468void RenderThemeChromiumDefault::adjustInnerSpinButtonStyle(RenderStyle* style, Element*) const
469{
470    IntSize size = Platform::current()->themeEngine()->getSize(WebThemeEngine::PartInnerSpinButton);
471
472    style->setWidth(Length(size.width(), Fixed));
473    style->setMinWidth(Length(size.width(), Fixed));
474}
475
476bool RenderThemeChromiumDefault::paintInnerSpinButton(RenderObject* o, const PaintInfo& i, const IntRect& rect)
477{
478    WebThemeEngine::ExtraParams extraParams;
479    WebCanvas* canvas = i.context->canvas();
480    extraParams.innerSpin.spinUp = (controlStatesForRenderer(o) & SpinUpControlState);
481    extraParams.innerSpin.readOnly = isReadOnlyControl(o);
482
483    Platform::current()->themeEngine()->paint(canvas, WebThemeEngine::PartInnerSpinButton, getWebThemeState(this, o), WebRect(rect), &extraParams);
484    return false;
485}
486
487bool RenderThemeChromiumDefault::paintProgressBar(RenderObject* o, const PaintInfo& i, const IntRect& rect)
488{
489    if (!o->isProgress())
490        return true;
491
492    RenderProgress* renderProgress = toRenderProgress(o);
493    IntRect valueRect = progressValueRectFor(renderProgress, rect);
494
495    WebThemeEngine::ExtraParams extraParams;
496    extraParams.progressBar.determinate = renderProgress->isDeterminate();
497    extraParams.progressBar.valueRectX = valueRect.x();
498    extraParams.progressBar.valueRectY = valueRect.y();
499    extraParams.progressBar.valueRectWidth = valueRect.width();
500    extraParams.progressBar.valueRectHeight = valueRect.height();
501
502    DirectionFlippingScope scope(o, i, rect);
503    WebCanvas* canvas = i.context->canvas();
504    Platform::current()->themeEngine()->paint(canvas, WebThemeEngine::PartProgressBar, getWebThemeState(this, o), WebRect(rect), &extraParams);
505    return false;
506}
507
508bool RenderThemeChromiumDefault::shouldOpenPickerWithF4Key() const
509{
510    return true;
511}
512
513bool RenderThemeChromiumDefault::shouldUseFallbackTheme(RenderStyle* style) const
514{
515    if (useMockTheme()) {
516        // The mock theme can't handle zoomed controls, so we fall back to the "fallback" theme.
517        ControlPart part = style->appearance();
518        if (part == CheckboxPart || part == RadioPart)
519            return style->effectiveZoom() != 1;
520    }
521    return RenderTheme::shouldUseFallbackTheme(style);
522}
523
524} // namespace blink
525