RenderThemeQtMobile.cpp   [plain text]


/*
 * This file is part of the WebKit project.
 *
 * Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies)
 *
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Library General Public
 * License as published by the Free Software Foundation; either
 * version 2 of the License, or (at your option) any later version.
 *
 * This library is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 * Library General Public License for more details.
 *
 * You should have received a copy of the GNU Library General Public License
 * along with this library; see the file COPYING.LIB.  If not, write to
 * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
 * Boston, MA 02110-1301, USA.
 *
 */

#include "config.h"
#include "RenderThemeQtMobile.h"

#include "CSSValueKeywords.h"
#include "Color.h"
#include "Document.h"
#include "Font.h"
#include "HTMLInputElement.h"
#include "HTMLNames.h"
#include "HTMLSelectElement.h"
#include "LocalizedStrings.h"
#include "NotImplemented.h"
#include "Page.h"
#include "PaintInfo.h"
#include "QWebPageClient.h"
#include "RenderBox.h"
#if ENABLE(PROGRESS_ELEMENT)
#include "RenderProgress.h"
#endif
#include "StyleResolver.h"

#include <wtf/PassRefPtr.h>

#include <QColor>
#include <QFile>
#include <QPainter>
#include <QPixmapCache>

namespace WebCore {

using namespace HTMLNames;

// Constants used by the mobile theme
static const int arrowBoxWidth = 26;
static const int frameWidth = 2;
static const int checkBoxWidth = 21;
static const int radioWidth = 21;
static const int sliderSize = 20;
static const int buttonHeightRatio = 1.5;

static const float multipleComboDotsOffsetFactor = 1.8;
static const float buttonPaddingLeft = 18;
static const float buttonPaddingRight = 18;
static const float buttonPaddingTop = 2;
static const float buttonPaddingBottom = 3;
static const float menuListPadding = 9;
static const float textFieldPadding = 10;
static const float radiusFactor = 0.36;
static const float progressBarChunkPercentage = 0.2;
#if ENABLE(PROGRESS_ELEMENT)
static const int progressAnimationGranularity = 2;
#endif
static const float sliderGrooveBorderRatio = 0.2;
static const QColor darkColor(40, 40, 40);
static const QColor highlightColor(16, 128, 221);
static const QColor buttonGradientBottom(245, 245, 245);
static const QColor shadowColor(80, 80, 80, 160);

static QHash<KeyIdentifier, CacheKey> cacheKeys;

static qreal painterScale(QPainter* painter)
{
    if (!painter)
        return 1;

    const QTransform& transform = painter->transform();
    qreal scale = 1;

    if (transform.type() == QTransform::TxScale)
        scale = qAbs(transform.m11());
    else if (transform.type() >= QTransform::TxRotate) {
        const QLineF l1(0, 0, 1, 0);
        const QLineF l2 = transform.map(l1);
        scale = qAbs(l2.length() / l1.length());
    }
    return scale;
}

uint qHash(const KeyIdentifier& id)
{
    const quint32 value = id.trait1 + (id.trait2 << 1) + (uint(id.type) << 2) + (id.height << 5) + (id.width << 14) + (id.trait3 << 25);
    const unsigned char* p = reinterpret_cast<const unsigned char*>(&value);
    uint hash = 0;
    for (int i = 0; i < 4; ++i)
        hash ^= (hash << 5) + (hash >> 2) + p[i];
    return hash;
}

/*
 * The octants' indices are identified below, for each point (x,y)
 * in the first octant, we can populate the 7 others with the corresponding
 * point.
 *
 *                                       index |   xpos   |   ypos
 *                xd                    ---------------------------
 *      4      |<--->| 3                    0  |  xd + x  |    y
 *     __________________                   1  |  xd + y  |    x
 *    /                  \                  2  |  xd + y  |   -x
 * 5 |         .(c)       |  2              3  |  xd + x  |   -y
 * 6 |                    |  1              4  | -xd - x  |   -y
 *    \__________________/                  5  | -xd - y  |   -x
 *                                          6  | -xd - y  |    x
 *      7              0                    7  | -xd - x  |    y
 *
 **/

static void addPointToOctants(QVector<QPainterPath>& octants, const QPointF& center, qreal x, qreal y , int xDelta = 0)
{
    ASSERT(octants.count() == 8);

    for (short i = 0; i < 8; ++i) {
        QPainterPath& octant = octants[i];
        QPointF pos(center);
        // The Gray code corresponding to the octant's index helps doing the math in a more generic way.
        const short gray = (i >> 1) ^ i;
        const qreal xOffset = xDelta + ((gray & 1) ? y : x);
        pos.ry() += ((gray & 2)? -1 : 1) * ((gray & 1) ? x : y);
        pos.rx() += (i < 4) ? xOffset : -xOffset;

        if (octant.elementCount())
            octant.lineTo(pos);
        else // The path is empty. Initialize the start point.
            octant.moveTo(pos);
    }
}

static void drawControlBackground(QPainter* painter, const QPen& pen, const QRect& rect, const QBrush& brush)
{
    QPen oldPen = painter->pen();
    QBrush oldBrush = painter->brush();
    painter->setRenderHint(QPainter::Antialiasing, true);
    painter->setPen(pen);
    painter->setBrush(brush);

    static const qreal line = 1.5;
    const QRectF paddedRect = rect.adjusted(line, line, -line, -line);

    static const int n = 3;
    const qreal invPow = 1 / double(n);
    ASSERT(paddedRect.width() >= paddedRect.height());
    const int radius = paddedRect.height() / 2;
    const int xDelta = paddedRect.width() / 2 - radius;
    const QPointF center = paddedRect.center();
    qreal x = 0;
    qreal y;
    QVector<QPainterPath> octants(8);
    // Stay within reasonable distance from edge values, which can cause artifacts at certain zoom levels.
    static const float epsilon = 0.02;
    for (y = radius - epsilon; y - epsilon > x; y -= 0.5) {
        x = radius * pow(1 - pow(qAbs(y) / radius , n), invPow);
        addPointToOctants(octants, center, x, y, xDelta);
    }

    QPainterPath path = octants.first();
    for (int i = 1; i < 8; ++i) {
        // Due to the orientation of the arcs, we need to reverse the paths with odd indices.
        QPainterPath subPath = (i % 2) ?  octants.at(i).toReversed() : octants.at(i);
        path.connectPath(subPath);
    }
    path.closeSubpath();

    painter->drawPath(path);
    painter->setPen(oldPen);
    painter->setBrush(oldBrush);
}

static inline QRect shrinkRectToSquare(const QRect& rect)
{
    const int side = qMin(rect.height(), rect.width());
    return QRect(rect.topLeft(), QSize(side, side));
}

static inline QPen borderPen(QPainter* painter = 0)
{
    return QPen(darkColor, qMin(1.0, 0.4 * painterScale(painter)));
}

QSharedPointer<StylePainter> RenderThemeQtMobile::getStylePainter(const PaintInfo& pi)
{
    return QSharedPointer<StylePainter>(new StylePainterMobile(this, pi));
}

QPalette RenderThemeQtMobile::colorPalette() const
{
    static QPalette lightGrayPalette(Qt::lightGray);
    return lightGrayPalette;
}

StylePainterMobile::StylePainterMobile(RenderThemeQtMobile* theme, const PaintInfo& paintInfo)
    : StylePainter(theme, paintInfo)
{
    m_previousSmoothPixmapTransform = painter->testRenderHint(QPainter::SmoothPixmapTransform);
    if (!m_previousSmoothPixmapTransform)
        painter->setRenderHint(QPainter::SmoothPixmapTransform);
}

StylePainterMobile::~StylePainterMobile()
{
    painter->setRenderHints(QPainter::SmoothPixmapTransform, m_previousSmoothPixmapTransform);
}

bool StylePainterMobile::findCachedControl(const KeyIdentifier& keyId, QPixmap* result)
{
    static CacheKey emptyKey;
    CacheKey key = cacheKeys.value(keyId, emptyKey);
    if (key == emptyKey)
        return false;
    const bool ret = QPixmapCache::find(key, result);
    if (!ret)
        cacheKeys.remove(keyId);
    return ret;
}

void StylePainterMobile::insertIntoCache(const KeyIdentifier& keyId, const QPixmap& pixmap)
{
    ASSERT(keyId.type);
    const int sizeInKiloBytes = pixmap.width() * pixmap.height() * pixmap.depth() / (8 * 1024);
    // Don't cache pixmaps over 512 KB;
    if (sizeInKiloBytes > 512)
        return;
    cacheKeys.insert(keyId, QPixmapCache::insert(pixmap));
}

void StylePainterMobile::drawCheckableBackground(QPainter* painter, const QRect& rect, bool checked, bool enabled) const
{
    QBrush brush;
    QColor color = Qt::gray;
    if (checked && enabled)
        color = highlightColor;

    QLinearGradient gradient;
    gradient.setStart(rect.topLeft());
    gradient.setFinalStop(rect.bottomLeft());
    gradient.setColorAt(0.0, color);
    gradient.setColorAt(1.0, color.lighter(130));
    brush = gradient;

    drawControlBackground(painter, borderPen(painter), rect, brush);
}

QSize StylePainterMobile::sizeForPainterScale(const QRect& rect) const
{
    qreal scale = painterScale(painter);
    QTransform scaleTransform = QTransform::fromScale(scale, scale);

    return scaleTransform.mapRect(rect).size();
}

void StylePainterMobile::drawChecker(QPainter* painter, const QRect& rect, const QColor& color) const
{
    painter->setRenderHint(QPainter::Antialiasing, true);
    QPen pen(Qt::darkGray);
    pen.setCosmetic(true);
    painter->setPen(pen);
    painter->scale(rect.width(), rect.height());
    QPainterPath path;
    path.moveTo(0.18, 0.47);
    path.lineTo(0.25, 0.4);
    path.lineTo(0.4, 0.55);
    path.quadTo(0.64, 0.29, 0.78, 0.2);
    path.lineTo(0.8, 0.25);
    path.quadTo(0.53, 0.55, 0.45, 0.75);
    path.closeSubpath();
    painter->setBrush(color);
    painter->drawPath(path);
}

QPixmap StylePainterMobile::findCheckBox(const QSize& size, bool checked, bool enabled) const
{
    ASSERT(size.width() == size.height());
    QPixmap result;
    KeyIdentifier id;
    id.type = KeyIdentifier::CheckBox;
    id.height = size.height();
    id.trait1 = enabled;
    id.trait2 = checked;
    if (!findCachedControl(id, &result)) {
        result = QPixmap(size);
        result.fill(Qt::transparent);
        QPainter cachePainter(&result);
        QRect rect(QPoint(0, 0), size);
        drawCheckableBackground(&cachePainter, rect, checked, enabled);
        if (checked || !enabled)
            drawChecker(&cachePainter, rect, enabled ? Qt::white : Qt::gray);
        insertIntoCache(id, result);
    }
    return result;
}

void StylePainterMobile::drawRadio(QPainter* painter, const QSize& size, bool checked, bool enabled) const
{
    QRect rect(QPoint(0, 0), size);

    drawCheckableBackground(painter, rect, checked, enabled);
    const int border = size.width() / 4;
    rect.adjust(border, border, -border, -border);
    drawControlBackground(painter, borderPen(), rect, enabled ? Qt::white : Qt::gray);
}

QPixmap StylePainterMobile::findRadio(const QSize& size, bool checked, bool enabled) const
{
    ASSERT(size.width() == size.height());
    QPixmap result;
    KeyIdentifier id;
    id.type = KeyIdentifier::Radio;
    id.height = size.height();
    id.trait1 = enabled;
    id.trait2 = checked;
    if (!findCachedControl(id, &result)) {
        result = QPixmap(size);
        result.fill(Qt::transparent);
        QPainter cachePainter(&result);
        drawRadio(&cachePainter, size, checked, enabled);
        insertIntoCache(id, result);
    }
    return result;
}

void StylePainterMobile::drawMultipleComboButton(QPainter* painter, const QSizeF& size, const QColor& color) const
{
    const qreal dotDiameter = size.height();
    const qreal dotRadii = dotDiameter / 2;

    painter->setRenderHint(QPainter::Antialiasing, true);
    painter->setPen(color);
    painter->setBrush(color);

    for (int i = 0; i < 3; ++i) {
        QPointF center(dotRadii + i * multipleComboDotsOffsetFactor * dotDiameter, dotRadii);
        painter->drawEllipse(center, dotRadii, dotRadii);
    }
}

void StylePainterMobile::drawSimpleComboButton(QPainter* painter, const QSizeF& size, const QColor& color) const
{
    const qreal gap = size.height() / 5.0;
    const qreal arrowHeight = (size.height() - gap) / 2.0;
    const qreal right = arrowHeight * 2;
    const qreal bottomBaseline = size.height() - arrowHeight;
    QPolygonF upArrow, downArrow;
    upArrow << QPointF(0, arrowHeight) << QPointF(arrowHeight, 0) << QPointF(right, arrowHeight);
    downArrow << QPointF(0, bottomBaseline) << QPointF(arrowHeight, bottomBaseline + arrowHeight)
              << QPointF(right, bottomBaseline);

    painter->setPen(Qt::NoPen);
    painter->setBrush(color);
    painter->drawPolygon(upArrow);
    painter->drawPolygon(downArrow);
}

QSizeF StylePainterMobile::getButtonImageSize(int buttonHeight, bool multiple) const
{
    if (multiple)
        return QSizeF(qreal(2 + buttonHeight * 3 * multipleComboDotsOffsetFactor/ 10.0)
                      , qreal(2 + buttonHeight / 10.0));

    const qreal height = buttonHeight / 2.5;
    const qreal width = 4 * height / 5.0;
    return QSizeF(2 + width, 2 + height);
}

QPixmap StylePainterMobile::findComboButton(const QSize& size, bool multiple, bool enabled) const
{
    if (size.isNull())
        return QPixmap();
    QPixmap result;
    KeyIdentifier id;
    id.type = KeyIdentifier::ComboButton;
    id.width = size.width();
    id.height = size.height();
    id.trait1 = multiple;
    id.trait2 = enabled;

    if (!findCachedControl(id, &result)) {
        result = QPixmap(size);
        const qreal border = painterScale(painter);
        const QSizeF padding(2 * border, 2 * border);
        const QSizeF innerSize = size - padding;
        ASSERT(innerSize.isValid());
        result.fill(Qt::transparent);
        QPainter cachePainter(&result);
        cachePainter.translate(border, border);
        if (multiple)
            drawMultipleComboButton(&cachePainter, innerSize, enabled ? darkColor : Qt::lightGray);
        else
            drawSimpleComboButton(&cachePainter, innerSize, enabled ? darkColor : Qt::lightGray);
        insertIntoCache(id, result);
    }
    return result;
}

void StylePainterMobile::drawLineEdit(const QRect& rect, bool focused, bool enabled)
{
    Q_UNUSED(enabled);
    QPixmap lineEdit = findLineEdit(sizeForPainterScale(rect), focused);
    if (lineEdit.isNull())
        return;
    painter->drawPixmap(rect, lineEdit);
}

QPixmap StylePainterMobile::findLineEdit(const QSize & size, bool focused) const
{
    QPixmap result;
    KeyIdentifier id;
    id.type = KeyIdentifier::LineEdit;
    id.width = size.width();
    id.height = size.height();
    id.trait1 = focused;

    if (!findCachedControl(id, &result)) {
        const int focusFrame = painterScale(painter);
        result = QPixmap(size);
        result.fill(Qt::transparent);
        const QRect rect = result.rect().adjusted(focusFrame, focusFrame, -focusFrame, -focusFrame);
        QPainter cachePainter(&result);
        drawControlBackground(&cachePainter, borderPen(painter), rect, Qt::white);

        if (focused) {
            QPen focusPen(highlightColor, 1.2 * painterScale(painter), Qt::SolidLine);
            drawControlBackground(&cachePainter, focusPen, rect, Qt::NoBrush);
        }
        insertIntoCache(id, result);
    }
    return result;
}

void StylePainterMobile::drawCheckBox(const QRect& rect, bool checked, bool enabled)
{
    const QRect square = shrinkRectToSquare(rect);
    QPixmap checkBox = findCheckBox(sizeForPainterScale(square), checked, enabled);
    if (checkBox.isNull())
        return;
    painter->drawPixmap(square, checkBox);
}

void StylePainterMobile::drawRadioButton(const QRect& rect, bool checked, bool enabled)
{
    const QRect square = shrinkRectToSquare(rect);
    QPixmap radio = findRadio(sizeForPainterScale(square), checked, enabled);
    if (radio.isNull())
        return;
    painter->drawPixmap(square, radio);
}

void StylePainterMobile::drawPushButton(const QRect& rect, bool sunken, bool enabled)
{
    QPixmap pushButton = findPushButton(sizeForPainterScale(rect), sunken, enabled);
    if (pushButton.isNull())
        return;
    painter->drawPixmap(rect, pushButton);
}

QPixmap StylePainterMobile::findPushButton(const QSize& size, bool sunken, bool enabled) const
{
    QPixmap result;
    KeyIdentifier id;
    id.type = KeyIdentifier::PushButton;
    id.width = size.width();
    id.height = size.height();
    id.trait1 = sunken;
    id.trait2 = enabled;
    if (!findCachedControl(id, &result)) {
        const qreal dropShadowSize = painterScale(painter);
        result = QPixmap(size);
        result.fill(Qt::transparent);
        const QRect rect = QRect(0, 0, size.width(), size.height() - dropShadowSize);
        QPainter cachePainter(&result);
        drawControlBackground(&cachePainter, Qt::NoPen, rect.adjusted(0, dropShadowSize, 0, dropShadowSize), shadowColor);

        QBrush brush;
        if (enabled && !sunken) {
            QLinearGradient linearGradient;
            linearGradient.setStart(rect.bottomLeft());
            linearGradient.setFinalStop(rect.topLeft());
            linearGradient.setColorAt(0.0, buttonGradientBottom);
            linearGradient.setColorAt(1.0, Qt::white);
            brush = linearGradient;
        } else if (!enabled)
            brush = QColor(241, 242, 243);
        else { // sunken
            QLinearGradient linearGradient;
            linearGradient.setStart(rect.bottomLeft());
            linearGradient.setFinalStop(rect.topLeft());
            linearGradient.setColorAt(0.0, highlightColor);
            linearGradient.setColorAt(1.0, highlightColor.lighter());
            brush = linearGradient;
        }
        drawControlBackground(&cachePainter, borderPen(painter), rect, brush);
        insertIntoCache(id, result);
    }
    return result;
}

void StylePainterMobile::drawComboBox(const QRect& rect, bool multiple, bool enabled)
{
    QPixmap pushButton = findPushButton(sizeForPainterScale(rect), /*sunken = */false, enabled);
    if (pushButton.isNull())
        return;
    painter->drawPixmap(rect, pushButton);
    QRectF targetRect(QPointF(0, 0), getButtonImageSize(rect.height() - 1, multiple));
    const QPointF buttonCenter(rect.right() - arrowBoxWidth / 2, rect.top() + (rect.height() - 1) / 2);
    targetRect.moveCenter(buttonCenter);
    QPixmap pic = findComboButton(sizeForPainterScale(targetRect.toRect()), multiple, enabled);
    if (pic.isNull())
        return;

    painter->drawPixmap(targetRect.toRect(), pic);
}

void StylePainterMobile::drawProgress(const QRect& rect, double progress, bool leftToRight, bool animated, bool vertical) const
{
    const int horizontalBorder = (vertical ? rect.width() / 4 : 0);
    const int verticalBorder = (vertical ? 0 : rect.height() / 4);
    const QRect targetRect = rect.adjusted(horizontalBorder, verticalBorder, -horizontalBorder, -verticalBorder);

    QPixmap result;
    QSize imageSize = sizeForPainterScale(targetRect);
    if (vertical)
        qSwap(imageSize.rheight(), imageSize.rwidth());
    KeyIdentifier id;
    id.type = KeyIdentifier::Progress;
    id.width = imageSize.width();
    id.height = imageSize.height();
    id.trait1 = animated;
    id.trait2 = (!animated && !leftToRight);
    id.trait3 = progress * 100;
    if (!findCachedControl(id, &result)) {
        if (imageSize.isNull())
            return;
        result = QPixmap(imageSize);
        result.fill(Qt::transparent);
        QPainter painter(&result);
        painter.setRenderHint(QPainter::Antialiasing);
        QRect progressRect(QPoint(0, 0), imageSize);
        qreal radius = radiusFactor * progressRect.height();
        painter.setBrush(Qt::NoBrush);
        painter.setPen(borderPen());
        progressRect.adjust(1, 1, -1, -1);
        painter.drawRoundedRect(progressRect, radius, radius);
        progressRect.adjust(1, 1, -1, -1);
        if (animated) {
            const int right = progressRect.right();
            const int startPos = right * (1 - progressBarChunkPercentage) * 2 * fabs(progress - 0.5);
            progressRect.setWidth(progressBarChunkPercentage * right);
            progressRect.moveLeft(startPos);
        } else {
            progressRect.setWidth(progress * progressRect.width());
            if (!leftToRight)
                progressRect.moveRight(imageSize.width() - 2);
        }
        if (progressRect.width() > 0) {
            QLinearGradient gradient;
            gradient.setStart(progressRect.bottomLeft());
            gradient.setFinalStop(progressRect.topLeft());
            gradient.setColorAt(0.0, highlightColor);
            gradient.setColorAt(1.0, highlightColor.lighter());
            painter.setBrush(gradient);
            painter.setPen(Qt::NoPen);
            radius = radiusFactor * progressRect.height();
            painter.drawRoundedRect(progressRect, radius, radius);
        }
        insertIntoCache(id, result);
    }
    QTransform transform;
    transform.rotate(-90);
    painter->drawPixmap(targetRect, vertical ? result.transformed(transform) : result);
}

void StylePainterMobile::drawSliderThumb(const QRect & rect, bool pressed) const
{
    QPixmap result;
    const QSize size = sizeForPainterScale(rect);
    KeyIdentifier id;
    id.type = KeyIdentifier::SliderThumb;
    id.width = size.width();
    id.height = size.height();
    id.trait1 = pressed;
    if (!findCachedControl(id, &result)) {
        if (size.isNull())
            return;
        result = QPixmap(size);
        result.fill(Qt::transparent);
        QPainter cachePainter(&result);
        drawControlBackground(&cachePainter, borderPen(painter), QRect(QPoint(0, 0), size), pressed? Qt::lightGray : buttonGradientBottom);
        insertIntoCache(id, result);
    }
    painter->drawPixmap(rect, result);
}


PassRefPtr<RenderTheme> RenderThemeQtMobile::create(Page* page)
{
    return adoptRef(new RenderThemeQtMobile(page));
}

RenderThemeQtMobile::RenderThemeQtMobile(Page* page)
    : RenderThemeQt(page)
{
}

RenderThemeQtMobile::~RenderThemeQtMobile()
{
}

bool RenderThemeQtMobile::isControlStyled(const RenderStyle* style, const BorderData& border, const FillLayer& fill, const Color& backgroundColor) const
{
    switch (style->appearance()) {
    case CheckboxPart:
    case RadioPart:
        return false;
    default:
        return RenderThemeQt::isControlStyled(style, border, fill, backgroundColor);
    }
}

int RenderThemeQtMobile::popupInternalPaddingBottom(RenderStyle* style) const
{
    return 1;
}

void RenderThemeQtMobile::computeSizeBasedOnStyle(RenderStyle* renderStyle) const
{
    QSize size(0, 0);

    switch (renderStyle->appearance()) {
    case TextAreaPart:
    case SearchFieldPart:
    case TextFieldPart: {
        int padding = frameWidth;
        renderStyle->setPaddingLeft(Length(padding, Fixed));
        renderStyle->setPaddingRight(Length(padding, Fixed));
        renderStyle->setPaddingTop(Length(padding, Fixed));
        renderStyle->setPaddingBottom(Length(padding, Fixed));
        break;
    }
    default:
        break;
    }
    // If the width and height are both specified, then we have nothing to do.
    if (!renderStyle->width().isIntrinsicOrAuto() && !renderStyle->height().isAuto())
        return;

    switch (renderStyle->appearance()) {
    case CheckboxPart: {
        const int w = checkBoxWidth * renderStyle->effectiveZoom();
        size = QSize(w, w);
        break;
    }
    case RadioPart: {
        const int w = radioWidth * renderStyle->effectiveZoom();
        size = QSize(w, w);
        break;
    }
    case PushButtonPart:
    case SquareButtonPart:
    case DefaultButtonPart:
    case ButtonPart:
    case MenulistPart: {
        const int height = renderStyle->fontMetrics().height() * buttonHeightRatio * renderStyle->effectiveZoom();
        size = QSize(renderStyle->width().value(), height);
        break;
    }
    default:
        break;
    }

    // FIXME: Check is flawed, since it doesn't take min-width/max-width into account.
    if (renderStyle->width().isIntrinsicOrAuto() && size.width() > 0)
        renderStyle->setMinWidth(Length(size.width(), Fixed));
    if (renderStyle->height().isAuto() && size.height() > 0)
        renderStyle->setMinHeight(Length(size.height(), Fixed));
}

void RenderThemeQtMobile::adjustButtonStyle(StyleResolver*, RenderStyle* style, Element*) const
{
    // Ditch the border.
    style->resetBorder();

    FontDescription fontDescription = style->fontDescription();
    fontDescription.setIsAbsoluteSize(true);

    fontDescription.setSpecifiedSize(style->fontSize());
    fontDescription.setComputedSize(style->fontSize());

    style->setLineHeight(RenderStyle::initialLineHeight());
    setButtonSize(style);
    setButtonPadding(style);
}

void RenderThemeQtMobile::setButtonPadding(RenderStyle* style) const
{
    if (!style)
        return;
    style->setPaddingLeft(Length(buttonPaddingLeft, Fixed));
    style->setPaddingRight(Length(buttonPaddingRight, Fixed));
    style->setPaddingTop(Length(buttonPaddingTop, Fixed));
    style->setPaddingBottom(Length(buttonPaddingBottom, Fixed));
}

bool RenderThemeQtMobile::paintButton(RenderObject* o, const PaintInfo& i, const IntRect& r)
{
    StylePainterMobile p(this, i);
    if (!p.isValid())
       return true;

    ControlPart appearance = o->style()->appearance();
    if (appearance == PushButtonPart || appearance == ButtonPart) {
        p.drawPushButton(r, isPressed(o), isEnabled(o));
    } else if (appearance == RadioPart)
       p.drawRadioButton(r, isChecked(o), isEnabled(o));
    else if (appearance == CheckboxPart)
       p.drawCheckBox(r, isChecked(o), isEnabled(o));

    return false;
}

void RenderThemeQtMobile::adjustTextFieldStyle(StyleResolver*, RenderStyle* style, Element*) const
{
    // Resetting the style like this leads to differences like:
    // - RenderTextControl {INPUT} at (2,2) size 168x25 [bgcolor=#FFFFFF] border: (2px inset #000000)]
    // + RenderTextControl {INPUT} at (2,2) size 166x26
    // in layout tests when a CSS style is applied that doesn't affect background color, border or
    // padding. Just worth keeping in mind!
    style->setBackgroundColor(Color::transparent);
    style->resetBorder();
    style->setBorderTopWidth(frameWidth);
    style->setBorderRightWidth(frameWidth);
    style->setBorderBottomWidth(frameWidth);
    style->setBorderLeftWidth(frameWidth);
    style->resetPadding();
    computeSizeBasedOnStyle(style);
    style->setPaddingLeft(Length(textFieldPadding, Fixed));
    style->setPaddingRight(Length(textFieldPadding, Fixed));
}

bool RenderThemeQtMobile::paintTextField(RenderObject* o, const PaintInfo& i, const IntRect& r)
{
    StylePainterMobile p(this, i);
    if (!p.isValid())
        return true;

    ControlPart appearance = o->style()->appearance();
    if (appearance != TextFieldPart
        && appearance != SearchFieldPart
        && appearance != TextAreaPart)
        return true;

    // Now paint the text field.
    if (appearance == TextAreaPart) {
        const bool previousAntialiasing = p.painter->testRenderHint(QPainter::Antialiasing);
        p.painter->setRenderHint(QPainter::Antialiasing);
        p.painter->setPen(borderPen());
        p.painter->setBrush(Qt::white);
        const int radius = checkBoxWidth * radiusFactor;
        p.painter->drawRoundedRect(r, radius, radius);

        if (isFocused(o)) {
            QPen focusPen(highlightColor, 1.0, Qt::SolidLine, Qt::RoundCap, Qt::RoundJoin);
            p.painter->setPen(focusPen);
            p.painter->setBrush(Qt::NoBrush);
            p.painter->drawRoundedRect(r, radius, radius);
        }
        p.painter->setRenderHint(QPainter::Antialiasing, previousAntialiasing);
    } else
        p.drawLineEdit(r, isFocused(o), isEnabled(o));
    return false;
}

void RenderThemeQtMobile::adjustMenuListStyle(StyleResolver* styleResolver, RenderStyle* style, Element* e) const
{
    RenderThemeQt::adjustMenuListStyle(styleResolver, style, e);
    style->setPaddingLeft(Length(menuListPadding, Fixed));
}

void RenderThemeQtMobile::setPopupPadding(RenderStyle* style) const
{
    const int paddingLeft = 4;
    const int paddingRight = style->width().isFixed() || style->width().isPercent() ? 5 : 8;

    style->setPaddingLeft(Length(paddingLeft, Fixed));
    style->setPaddingRight(Length(paddingRight + arrowBoxWidth, Fixed));

    style->setPaddingTop(Length(2, Fixed));
    style->setPaddingBottom(Length(2, Fixed));
}

bool RenderThemeQtMobile::paintMenuList(RenderObject* o, const PaintInfo& i, const IntRect& r)
{
    StylePainterMobile p(this, i);
    if (!p.isValid())
        return true;

    p.drawComboBox(r, checkMultiple(o), isEnabled(o));
    return false;
}

bool RenderThemeQtMobile::paintMenuListButton(RenderObject* o, const PaintInfo& i,
                                        const IntRect& r)
{
    StylePainterMobile p(this, i);
    if (!p.isValid())
        return true;

    p.drawComboBox(r, checkMultiple(o), isEnabled(o));

    return false;
}

#if ENABLE(PROGRESS_ELEMENT)
double RenderThemeQtMobile::animationDurationForProgressBar(RenderProgress* renderProgress) const
{
    if (renderProgress->isDeterminate())
        return 0;
    // Our animation goes back and forth so we need to make it last twice as long
    // and we need the numerator to be an odd number to ensure we get a progress value of 0.5.
    return (2 * progressAnimationGranularity +1) / progressBarChunkPercentage * animationRepeatIntervalForProgressBar(renderProgress);
}

bool RenderThemeQtMobile::paintProgressBar(RenderObject* o, const PaintInfo& pi, const IntRect& r)
{
    if (!o->isProgress())
        return true;

    StylePainterMobile p(this, pi);
    if (!p.isValid())
        return true;

    RenderProgress* renderProgress = toRenderProgress(o);
    const bool isRTL = (renderProgress->style()->direction() == RTL);

    if (renderProgress->isDeterminate())
        p.drawProgress(r, renderProgress->position(), !isRTL);
    else
        p.drawProgress(r, renderProgress->animationProgress(), !isRTL, true);

    return false;
}
#endif

bool RenderThemeQtMobile::paintSliderTrack(RenderObject* o, const PaintInfo& pi,
                                     const IntRect& r)
{
    StylePainterMobile p(this, pi);
    if (!p.isValid())
        return true;

    HTMLInputElement* slider = static_cast<HTMLInputElement*>(o->node());

    const double min = slider->minimum();
    const double max = slider->maximum();
    const double progress = (max - min > 0) ? (slider->valueAsNumber() - min) / (max - min) : 0;

    QRect rect(r);
    const bool vertical = (o->style()->appearance() == SliderVerticalPart);
    const int groovePadding = vertical ? r.width() * sliderGrooveBorderRatio : r.height() * sliderGrooveBorderRatio;
    if (vertical) {
        rect.adjust(groovePadding, 0, -groovePadding, 0);
        // Direction is ignored on vertical sliders and we assume LTR.
        p.drawProgress(rect, progress, true, /*animated = */ false, vertical);
    } else {
        rect.adjust(0, groovePadding, 0, -groovePadding);
        p.drawProgress(rect, progress, o->style()->isLeftToRightDirection(), /*animated = */ false, vertical);
    }

    return false;
}

bool RenderThemeQtMobile::paintSliderThumb(RenderObject* o, const PaintInfo& pi,
                                     const IntRect& r)
{
    StylePainterMobile p(this, pi);
    if (!p.isValid())
        return true;

    p.drawSliderThumb(r, isPressed(o));

    return false;
}

bool RenderThemeQtMobile::checkMultiple(RenderObject* o) const
{
    HTMLSelectElement* select = o ? toHTMLSelectElement(o->node()) : 0;
    return select ? select->multiple() : false;
}

void RenderThemeQtMobile::adjustSliderThumbSize(RenderStyle* style, Element* element) const
{
    const ControlPart part = style->appearance();
    if (part == SliderThumbHorizontalPart || part == SliderThumbVerticalPart) {
        const int size = sliderSize * style->effectiveZoom();
        style->setWidth(Length(size, Fixed));
        style->setHeight(Length(size, Fixed));
    } else
        RenderThemeQt::adjustSliderThumbSize(style, element);
}

}

// vim: ts=4 sw=4 et