1/*
2 * This file is part of the WebKit project.
3 *
4 * Copyright (C) 2008-2012 Nokia Corporation and/or its subsidiary(-ies)
5 *
6 * Copyright (C) 2006 Zack Rusin <zack@kde.org>
7 *               2006 Dirk Mueller <mueller@kde.org>
8 *               2006 Nikolas Zimmermann <zimmermann@kde.org>
9 * Copyright (C) 2008 Holger Hans Peter Freyther
10 *
11 * All rights reserved.
12 *
13 * This library is free software; you can redistribute it and/or
14 * modify it under the terms of the GNU Library General Public
15 * License as published by the Free Software Foundation; either
16 * version 2 of the License, or (at your option) any later version.
17 *
18 * This library is distributed in the hope that it will be useful,
19 * but WITHOUT ANY WARRANTY; without even the implied warranty of
20 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
21 * Library General Public License for more details.
22 *
23 * You should have received a copy of the GNU Library General Public License
24 * along with this library; see the file COPYING.LIB.  If not, write to
25 * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
26 * Boston, MA 02110-1301, USA.
27 *
28 */
29
30#include "config.h"
31#include "RenderThemeQStyle.h"
32
33#include "CSSFontSelector.h"
34#include "CSSValueKeywords.h"
35#include "Chrome.h"
36#include "ChromeClient.h"
37#include "Color.h"
38#include "Document.h"
39#include "Font.h"
40#include "FontSelector.h"
41#include "GraphicsContext.h"
42#include "HTMLInputElement.h"
43#include "HTMLNames.h"
44#include "LocalizedStrings.h"
45#include "NotImplemented.h"
46#include "Page.h"
47#include "PaintInfo.h"
48#include "QWebPageClient.h"
49#include "RenderBox.h"
50#if ENABLE(PROGRESS_ELEMENT)
51#include "RenderProgress.h"
52#endif
53#include "RenderSlider.h"
54#include "ScrollbarThemeQStyle.h"
55#include "SliderThumbElement.h"
56#include "StyleResolver.h"
57#include "UserAgentStyleSheets.h"
58
59#include <QPainter>
60
61namespace WebCore {
62
63using namespace HTMLNames;
64
65QSharedPointer<StylePainter> RenderThemeQStyle::getStylePainter(const PaintInfo& paintInfo)
66{
67    return QSharedPointer<StylePainter>(new StylePainterQStyle(this, paintInfo, /*RenderObject*/0));
68}
69
70StylePainterQStyle::StylePainterQStyle(RenderThemeQStyle* theme, const PaintInfo& paintInfo, RenderObject* renderObject)
71    : StylePainter(theme, paintInfo)
72    , qStyle(theme->qStyle())
73    , appearance(NoControlPart)
74{
75    init(paintInfo.context ? paintInfo.context : 0);
76    if (renderObject)
77        appearance = theme->initializeCommonQStyleOptions(styleOption, renderObject);
78}
79
80StylePainterQStyle::StylePainterQStyle(ScrollbarThemeQStyle* theme, GraphicsContext* context)
81    : StylePainter()
82    , qStyle(theme->qStyle())
83    , appearance(NoControlPart)
84{
85    init(context);
86}
87
88void StylePainterQStyle::init(GraphicsContext* context)
89{
90    painter = static_cast<QPainter*>(context->platformContext());
91    if (QObject* widget = qStyle->widgetForPainter(painter)) {
92        styleOption.palette = widget->property("palette").value<QPalette>();
93        styleOption.rect = widget->property("rect").value<QRect>();
94        styleOption.direction = static_cast<Qt::LayoutDirection>(widget->property("layoutDirection").toInt());
95    }
96
97    StylePainter::init(context);
98}
99
100PassRefPtr<RenderTheme> RenderThemeQStyle::create(Page* page)
101{
102    return adoptRef(new RenderThemeQStyle(page));
103}
104
105static QtStyleFactoryFunction styleFactoryFunction;
106
107void RenderThemeQStyle::setStyleFactoryFunction(QtStyleFactoryFunction function)
108{
109    styleFactoryFunction = function;
110}
111
112QtStyleFactoryFunction RenderThemeQStyle::styleFactory()
113{
114    return styleFactoryFunction;
115}
116
117RenderThemeQStyle::RenderThemeQStyle(Page* page)
118    : RenderThemeQt(page)
119    , m_qStyle(adoptPtr(styleFactoryFunction(page)))
120{
121    int buttonPixelSize = 0;
122    m_qStyle->getButtonMetrics(&m_buttonFontFamily, &buttonPixelSize);
123#ifdef Q_WS_MAC
124    m_buttonFontPixelSize = buttonPixelSize;
125#endif
126}
127
128RenderThemeQStyle::~RenderThemeQStyle()
129{
130}
131
132void RenderThemeQStyle::setPaletteFromPageClientIfExists(QPalette& palette) const
133{
134    if (!m_page)
135        return;
136
137    ChromeClient* chromeClient = m_page->chrome().client();
138    if (!chromeClient)
139        return;
140
141    if (QWebPageClient* pageClient = chromeClient->platformPageClient())
142        palette = pageClient->palette();
143}
144
145QRect RenderThemeQStyle::inflateButtonRect(const QRect& originalRect) const
146{
147    QRect layoutRect = m_qStyle->buttonSubElementRect(QStyleFacade::PushButtonLayoutItem, QStyleFacade::State_Small, originalRect);
148    if (!layoutRect.isNull()) {
149        int paddingLeft = layoutRect.left() - originalRect.left();
150        int paddingRight = originalRect.right() - layoutRect.right();
151        int paddingTop = layoutRect.top() - originalRect.top();
152        int paddingBottom = originalRect.bottom() - layoutRect.bottom();
153
154        return originalRect.adjusted(-paddingLeft, -paddingTop, paddingRight, paddingBottom);
155    }
156    return originalRect;
157}
158
159void RenderThemeQStyle::computeSizeBasedOnStyle(RenderStyle* renderStyle) const
160{
161    QSize size(0, 0);
162    const QFontMetrics fm(renderStyle->font().syntheticFont());
163
164    switch (renderStyle->appearance()) {
165    case TextAreaPart:
166    case SearchFieldPart:
167    case TextFieldPart: {
168        int padding = m_qStyle->findFrameLineWidth();
169        renderStyle->setPaddingLeft(Length(padding, Fixed));
170        renderStyle->setPaddingRight(Length(padding, Fixed));
171        renderStyle->setPaddingTop(Length(padding, Fixed));
172        renderStyle->setPaddingBottom(Length(padding, Fixed));
173        break;
174    }
175    default:
176        break;
177    }
178    // If the width and height are both specified, then we have nothing to do.
179    if (!renderStyle->width().isIntrinsicOrAuto() && !renderStyle->height().isAuto())
180        return;
181
182    switch (renderStyle->appearance()) {
183    case CheckboxPart: {
184        int checkBoxWidth = m_qStyle->simplePixelMetric(QStyleFacade::PM_IndicatorWidth, QStyleFacade::State_Small);
185        checkBoxWidth *= renderStyle->effectiveZoom();
186        size = QSize(checkBoxWidth, checkBoxWidth);
187        break;
188    }
189    case RadioPart: {
190        int radioWidth = m_qStyle->simplePixelMetric(QStyleFacade::PM_ExclusiveIndicatorWidth, QStyleFacade::State_Small);
191        radioWidth *= renderStyle->effectiveZoom();
192        size = QSize(radioWidth, radioWidth);
193        break;
194    }
195    case PushButtonPart:
196    case ButtonPart: {
197        QSize contentSize = fm.size(Qt::TextShowMnemonic, QString::fromLatin1("X"));
198        QSize pushButtonSize = m_qStyle->pushButtonSizeFromContents(QStyleFacade::State_Small, contentSize);
199        QRect layoutRect = m_qStyle->buttonSubElementRect(QStyleFacade::PushButtonLayoutItem, QStyleFacade::State_Small, QRect(0, 0, pushButtonSize.width(), pushButtonSize.height()));
200
201        // If the style supports layout rects we use that, and  compensate accordingly
202        // in paintButton() below.
203        if (!layoutRect.isNull())
204            size.setHeight(layoutRect.height());
205        else
206            size.setHeight(pushButtonSize.height());
207
208        break;
209    }
210    case MenulistPart: {
211        int contentHeight = qMax(fm.lineSpacing(), 14) + 2;
212        QSize menuListSize = m_qStyle->comboBoxSizeFromContents(QStyleFacade::State_Small, QSize(0, contentHeight));
213        size.setHeight(menuListSize.height());
214        break;
215    }
216    default:
217        break;
218    }
219
220    // FIXME: Check is flawed, since it doesn't take min-width/max-width into account.
221    if (renderStyle->width().isIntrinsicOrAuto() && size.width() > 0)
222        renderStyle->setMinWidth(Length(size.width(), Fixed));
223    if (renderStyle->height().isAuto() && size.height() > 0)
224        renderStyle->setMinHeight(Length(size.height(), Fixed));
225}
226
227
228
229void RenderThemeQStyle::adjustButtonStyle(StyleResolver* styleResolver, RenderStyle* style, Element*) const
230{
231    // Ditch the border.
232    style->resetBorder();
233
234#ifdef Q_WS_MAC
235    if (style->appearance() == PushButtonPart) {
236        // The Mac ports ignore the specified height for <input type="button"> elements
237        // unless a border and/or background CSS property is also specified.
238        style->setHeight(Length(Auto));
239    }
240#endif
241
242    FontDescription fontDescription = style->fontDescription();
243    fontDescription.setIsAbsoluteSize(true);
244
245#ifdef Q_WS_MAC // Use fixed font size and family on Mac (like Safari does)
246    fontDescription.setSpecifiedSize(m_buttonFontPixelSize);
247    fontDescription.setComputedSize(m_buttonFontPixelSize);
248#else
249    fontDescription.setSpecifiedSize(style->fontSize());
250    fontDescription.setComputedSize(style->fontSize());
251#endif
252
253    Vector<AtomicString, 1> families;
254    families.append(m_buttonFontFamily);
255    fontDescription.setFamilies(families);
256    style->setFontDescription(fontDescription);
257    style->font().update(styleResolver->fontSelector());
258    style->setLineHeight(RenderStyle::initialLineHeight());
259    setButtonSize(style);
260    setButtonPadding(style);
261}
262
263void RenderThemeQStyle::setButtonPadding(RenderStyle* style) const
264{
265    // Fake a button rect here, since we're just computing deltas
266    QRect originalRect = QRect(0, 0, 100, 30);
267
268    // Default padding is based on the button margin pixel metric
269    int buttonMargin = m_qStyle->buttonMargin(QStyleFacade::State_Small, originalRect);
270    int paddingLeft = buttonMargin;
271    int paddingRight = buttonMargin;
272    int paddingTop = buttonMargin;
273    int paddingBottom = buttonMargin;
274
275    // Then check if the style uses layout margins
276    QRect layoutRect = m_qStyle->buttonSubElementRect(QStyleFacade::PushButtonLayoutItem, QStyleFacade::State_Small, originalRect);
277    if (!layoutRect.isNull()) {
278        QRect contentsRect = m_qStyle->buttonSubElementRect(QStyleFacade::PushButtonContents, QStyleFacade::State_Small, originalRect);
279        paddingLeft = contentsRect.left() - layoutRect.left();
280        paddingRight = layoutRect.right() - contentsRect.right();
281        paddingTop = contentsRect.top() - layoutRect.top();
282
283        // Can't use this right now because we don't have the baseline to compensate
284        // paddingBottom = layoutRect.bottom() - contentsRect.bottom();
285    }
286    style->setPaddingLeft(Length(paddingLeft, Fixed));
287    style->setPaddingRight(Length(paddingRight, Fixed));
288    style->setPaddingTop(Length(paddingTop, Fixed));
289    style->setPaddingBottom(Length(paddingBottom, Fixed));
290}
291
292bool RenderThemeQStyle::paintButton(RenderObject* o, const PaintInfo& i, const IntRect& r)
293{
294    StylePainterQStyle p(this, i, o);
295    if (!p.isValid())
296        return true;
297
298    p.styleOption.rect = r;
299    p.styleOption.state |= QStyleFacade::State_Small;
300
301    if (p.appearance == PushButtonPart || p.appearance == ButtonPart) {
302        p.styleOption.rect = inflateButtonRect(p.styleOption.rect);
303        p.paintButton(QStyleFacade::PushButton);
304    } else if (p.appearance == RadioPart)
305        p.paintButton(QStyleFacade::RadioButton);
306    else if (p.appearance == CheckboxPart)
307        p.paintButton(QStyleFacade::CheckBox);
308
309    return false;
310}
311
312bool RenderThemeQStyle::paintTextField(RenderObject* o, const PaintInfo& i, const IntRect& r)
313{
314    StylePainterQStyle p(this, i, o);
315    if (!p.isValid())
316        return true;
317
318    p.styleOption.rect = r;
319    p.styleOption.state |= QStyleFacade::State_Sunken;
320
321    // Get the correct theme data for a text field
322    if (p.appearance != TextFieldPart
323        && p.appearance != SearchFieldPart
324        && p.appearance != TextAreaPart
325        && p.appearance != ListboxPart)
326        return true;
327
328    // Now paint the text field.
329    p.paintTextField();
330    return false;
331}
332
333void RenderThemeQStyle::adjustTextAreaStyle(StyleResolver* styleResolver, RenderStyle* style, Element* element) const
334{
335    adjustTextFieldStyle(styleResolver, style, element);
336}
337
338bool RenderThemeQStyle::paintTextArea(RenderObject* o, const PaintInfo& i, const IntRect& r)
339{
340    return paintTextField(o, i, r);
341}
342
343void RenderThemeQStyle::setPopupPadding(RenderStyle* style) const
344{
345    const int paddingLeft = 4;
346    const int paddingRight = style->width().isFixed() || style->width().isPercent() ? 5 : 8;
347
348    style->setPaddingLeft(Length(paddingLeft, Fixed));
349
350    int w = m_qStyle->simplePixelMetric(QStyleFacade::PM_ButtonIconSize);
351    style->setPaddingRight(Length(paddingRight + w, Fixed));
352
353    style->setPaddingTop(Length(2, Fixed));
354    style->setPaddingBottom(Length(2, Fixed));
355}
356
357QPalette RenderThemeQStyle::colorPalette() const
358{
359    QPalette palette = RenderThemeQt::colorPalette();
360    setPaletteFromPageClientIfExists(palette);
361    return palette;
362}
363
364bool RenderThemeQStyle::paintMenuList(RenderObject* o, const PaintInfo& i, const IntRect& r)
365{
366    StylePainterQStyle p(this, i, o);
367    if (!p.isValid())
368        return true;
369
370    p.styleOption.rect = r;
371    p.paintComboBox();
372    return false;
373}
374
375void RenderThemeQStyle::adjustMenuListButtonStyle(StyleResolver* styleResolver, RenderStyle* style, Element* e) const
376{
377    // WORKAROUND because html.css specifies -webkit-border-radius for <select> so we override it here
378    // see also http://bugs.webkit.org/show_bug.cgi?id=18399
379    style->resetBorderRadius();
380
381    RenderThemeQt::adjustMenuListButtonStyle(styleResolver, style, e);
382}
383
384bool RenderThemeQStyle::paintMenuListButton(RenderObject* o, const PaintInfo& i, const IntRect& r)
385{
386    StylePainterQStyle p(this, i, o);
387    if (!p.isValid())
388        return true;
389
390    p.styleOption.rect = r;
391    p.paintComboBoxArrow();
392    return false;
393}
394
395#if ENABLE(PROGRESS_ELEMENT)
396double RenderThemeQStyle::animationDurationForProgressBar(RenderProgress* renderProgress) const
397{
398    if (renderProgress->position() >= 0)
399        return 0;
400
401    IntSize size = renderProgress->pixelSnappedSize();
402    // FIXME: Until http://bugreports.qt.nokia.com/browse/QTBUG-9171 is fixed,
403    // we simulate one square animating across the progress bar.
404    return (size.width() / m_qStyle->progressBarChunkWidth(size)) * animationRepeatIntervalForProgressBar(renderProgress);
405}
406
407bool RenderThemeQStyle::paintProgressBar(RenderObject* o, const PaintInfo& pi, const IntRect& r)
408{
409    if (!o->isProgress())
410        return true;
411
412    StylePainterQStyle p(this, pi, o);
413    if (!p.isValid())
414        return true;
415
416    p.styleOption.rect = r;
417    RenderProgress* renderProgress = toRenderProgress(o);
418    p.paintProgressBar(renderProgress->position(), renderProgress->animationProgress());
419    return false;
420}
421#endif
422
423bool RenderThemeQStyle::paintSliderTrack(RenderObject* o, const PaintInfo& pi, const IntRect& r)
424{
425    StylePainterQStyle p(this, pi, o);
426    if (!p.isValid())
427        return true;
428
429    const QPoint topLeft = r.location();
430    p.painter->translate(topLeft);
431
432    p.styleOption.rect = r;
433    p.styleOption.rect.moveTo(QPoint(0, 0));
434
435    if (p.appearance == SliderVerticalPart)
436        p.styleOption.slider.orientation = Qt::Vertical;
437
438    if (isPressed(o))
439        p.styleOption.state |= QStyleFacade::State_Sunken;
440
441    // some styles need this to show a highlight on one side of the groove
442    HTMLInputElement* slider = o->node()->toInputElement();
443    if (slider) {
444        p.styleOption.slider.upsideDown = (p.appearance == SliderHorizontalPart) && !o->style()->isLeftToRightDirection();
445        // Use the width as a multiplier in case the slider values are <= 1
446        const int width = r.width() > 0 ? r.width() : 100;
447        p.styleOption.slider.maximum = slider->maximum() * width;
448        p.styleOption.slider.minimum = slider->minimum() * width;
449        if (!p.styleOption.slider.upsideDown)
450            p.styleOption.slider.position = slider->valueAsNumber() * width;
451        else
452            p.styleOption.slider.position = p.styleOption.slider.minimum + p.styleOption.slider.maximum - slider->valueAsNumber() * width;
453    }
454
455    p.paintSliderTrack();
456
457    p.painter->translate(-topLeft);
458    return false;
459}
460
461void RenderThemeQStyle::adjustSliderTrackStyle(StyleResolver*, RenderStyle* style, Element*) const
462{
463    style->setBoxShadow(nullptr);
464}
465
466bool RenderThemeQStyle::paintSliderThumb(RenderObject* o, const PaintInfo& pi, const IntRect& r)
467{
468    StylePainterQStyle p(this, pi, o);
469    if (!p.isValid())
470        return true;
471
472    const QPoint topLeft = r.location();
473    p.painter->translate(topLeft);
474
475    p.styleOption.rect = r;
476    p.styleOption.rect.moveTo(QPoint(0, 0));
477    p.styleOption.slider.orientation = Qt::Horizontal;
478    if (p.appearance == SliderThumbVerticalPart)
479        p.styleOption.slider.orientation = Qt::Vertical;
480    if (isPressed(o))
481        p.styleOption.state |= QStyleFacade::State_Sunken;
482
483    p.paintSliderThumb();
484
485    p.painter->translate(-topLeft);
486    return false;
487}
488
489void RenderThemeQStyle::adjustSliderThumbStyle(StyleResolver* styleResolver, RenderStyle* style, Element* element) const
490{
491    RenderTheme::adjustSliderThumbStyle(styleResolver, style, element);
492    style->setBoxShadow(nullptr);
493}
494
495bool RenderThemeQStyle::paintSearchField(RenderObject* o, const PaintInfo& pi, const IntRect& r)
496{
497    return paintTextField(o, pi, r);
498}
499
500void RenderThemeQStyle::adjustSearchFieldDecorationStyle(StyleResolver* styleResolver, RenderStyle* style, Element* e) const
501{
502    notImplemented();
503    RenderTheme::adjustSearchFieldDecorationStyle(styleResolver, style, e);
504}
505
506bool RenderThemeQStyle::paintSearchFieldDecoration(RenderObject* o, const PaintInfo& pi, const IntRect& r)
507{
508    notImplemented();
509    return RenderTheme::paintSearchFieldDecoration(o, pi, r);
510}
511
512void RenderThemeQStyle::adjustSearchFieldResultsDecorationStyle(StyleResolver* styleResolver, RenderStyle* style, Element* e) const
513{
514    notImplemented();
515    RenderTheme::adjustSearchFieldResultsDecorationStyle(styleResolver, style, e);
516}
517
518bool RenderThemeQStyle::paintSearchFieldResultsDecoration(RenderObject* o, const PaintInfo& pi, const IntRect& r)
519{
520    notImplemented();
521    return RenderTheme::paintSearchFieldResultsDecoration(o, pi, r);
522}
523
524#ifndef QT_NO_SPINBOX
525
526bool RenderThemeQStyle::paintInnerSpinButton(RenderObject* o, const PaintInfo& paintInfo, const IntRect& rect)
527{
528    StylePainterQStyle p(this, paintInfo, o);
529    if (!p.isValid())
530        return true;
531
532    p.styleOption.rect = rect;
533    p.paintInnerSpinButton(isSpinUpButtonPartPressed(o));
534    return false;
535}
536#endif
537
538ControlPart RenderThemeQStyle::initializeCommonQStyleOptions(QStyleFacadeOption &option, RenderObject* o) const
539{
540    // Default bits: no focus, no mouse over, enabled
541    option.state &= ~(QStyleFacade::State_HasFocus | QStyleFacade::State_MouseOver);
542    option.state |= QStyleFacade::State_Enabled;
543
544    if (isReadOnlyControl(o))
545        // Readonly is supported on textfields.
546        option.state |= QStyleFacade::State_ReadOnly;
547
548    option.direction = Qt::LeftToRight;
549
550    if (isHovered(o))
551        option.state |= QStyleFacade::State_MouseOver;
552
553    setPaletteFromPageClientIfExists(option.palette);
554
555    if (!isEnabled(o)) {
556        option.palette.setCurrentColorGroup(QPalette::Disabled);
557        option.state &= ~QStyleFacade::State_Enabled;
558    }
559
560    RenderStyle* style = o->style();
561    if (!style)
562        return NoControlPart;
563
564    ControlPart result = style->appearance();
565    if (supportsFocus(result) && isFocused(o)) {
566        option.state |= QStyleFacade::State_HasFocus;
567        option.state |= QStyleFacade::State_KeyboardFocusChange;
568    }
569
570    if (style->direction() == WebCore::RTL)
571        option.direction = Qt::RightToLeft;
572
573    switch (result) {
574    case PushButtonPart:
575    case SquareButtonPart:
576    case ButtonPart:
577    case ButtonBevelPart:
578    case ListItemPart:
579    case MenulistButtonPart:
580    case InnerSpinButtonPart:
581    case SearchFieldResultsButtonPart:
582    case SearchFieldCancelButtonPart: {
583        if (isPressed(o))
584            option.state |= QStyleFacade::State_Sunken;
585        else if (result == PushButtonPart || result == ButtonPart)
586            option.state |= QStyleFacade::State_Raised;
587        break;
588    }
589    case RadioPart:
590    case CheckboxPart:
591        option.state |= (isChecked(o) ? QStyleFacade::State_On : QStyleFacade::State_Off);
592    }
593
594    return result;
595}
596
597void RenderThemeQStyle::adjustSliderThumbSize(RenderStyle* style, Element* element) const
598{
599    const ControlPart part = style->appearance();
600    if (part == SliderThumbHorizontalPart || part == SliderThumbVerticalPart) {
601        Qt::Orientation orientation = Qt::Horizontal;
602        if (part == SliderThumbVerticalPart)
603            orientation = Qt::Vertical;
604
605        int length = m_qStyle->sliderLength(orientation);
606        int thickness = m_qStyle->sliderThickness(orientation);
607        if (orientation == Qt::Vertical) {
608            style->setWidth(Length(thickness, Fixed));
609            style->setHeight(Length(length, Fixed));
610        } else {
611            style->setWidth(Length(length, Fixed));
612            style->setHeight(Length(thickness, Fixed));
613        }
614    } else
615        RenderThemeQt::adjustSliderThumbSize(style, element);
616}
617
618}
619
620// vim: ts=4 sw=4 et
621