src/hbcore/primitives/hbtextitem.cpp
author Dremov Kirill (Nokia-D-MSW/Tampere) <kirill.dremov@nokia.com>
Wed, 23 Jun 2010 18:33:25 +0300
changeset 6 c3690ec91ef8
parent 3 11d3954df52a
child 7 923ff622b8b9
permissions -rw-r--r--
Revision: 201023 Kit: 2010125

/****************************************************************************
**
** Copyright (C) 2008-2010 Nokia Corporation and/or its subsidiary(-ies).
** All rights reserved.
** Contact: Nokia Corporation (developer.feedback@nokia.com)
**
** This file is part of the HbCore module of the UI Extensions for Mobile.
**
** GNU Lesser General Public License Usage
** This file may be used under the terms of the GNU Lesser General Public
** License version 2.1 as published by the Free Software Foundation and
** appearing in the file LICENSE.LGPL included in the packaging of this file.
** Please review the following information to ensure the GNU Lesser General
** Public License version 2.1 requirements will be met:
** http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
**
** In addition, as a special exception, Nokia gives you certain additional
** rights.  These rights are described in the Nokia Qt LGPL Exception
** version 1.1, included in the file LGPL_EXCEPTION.txt in this package.
**
** If you have questions regarding the use of this file, please contact
** Nokia at developer.feedback@nokia.com.
**
****************************************************************************/

#include "hbtextitem.h"
#include "hbtextitem_p.h"
#include "hbstyle.h"
#include "hbtextutils_p.h"
#include "hbcolorscheme.h"
#include "hbevent.h"

#include <QTextLayout>
#include <QGraphicsSceneResizeEvent>
#include <QPainter>
#include <QTextOption>
#include <QApplication>

#ifdef HB_TEXT_MEASUREMENT_UTILITY
#include "hbtextmeasurementutility_p.h"
#include "hbfeaturemanager_r.h"
#endif

// #define HB_TEXT_ITEM_LOGS
#define EPSILON 0.01

#ifdef HB_TEXT_ITEM_LOGS
#   include <QDebug>
#endif // HB_TEXT_ITEM_LOGS

bool HbTextItemPrivate::outlinesEnabled = false;

static const QString KDefaultColorThemeName = "qtc_view_normal";
const qreal MinimumWidth = 5.0; // minimum width if there is some text.
const int KLayoutCacheLimit = 64;
const qreal KFadeTolerance = 1.0;

HbTextItemPrivate::HbTextItemPrivate () :
    mAlignment(Qt::AlignLeft | Qt::AlignVCenter),
    mElideMode(Qt::ElideNone),
    mInvalidateShownText(true),
    mOffsetPos(0,0),
    mPaintFaded(false),
    mFadeLengthX(30),
    mFadeLengthY(15),
    mMinLines(1),
    mMaxLines(0),
    mMinWidthForAdjust(-1),
    mMaxWidthForAdjust(-1),
    mDefaultHeight(-1),
    mUpdateColor(true),
    mEventPosted(false)
{
}

void HbTextItemPrivate::init(QGraphicsItem *)
{
    Q_Q(HbTextItem);

    q->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Fixed);
    q->setFlag(QGraphicsItem::ItemClipsToShape, false);
    q->setFlag(QGraphicsItem::ItemIsSelectable, false);
    q->setFlag(QGraphicsItem::ItemIsFocusable,  false);

    QTextOption textOption = mTextLayout.textOption();
    textOption.setWrapMode(QTextOption::WordWrap);
    mTextLayout.setTextOption(textOption);
    mTextLayout.setCacheEnabled(true);
    mTextLayout.setFont(q->font());
}

void HbTextItemPrivate::clear()
{
    // no implementation needed
}

bool HbTextItemPrivate::doLayout(const QString& text, const qreal lineWidth, qreal lineSpacing)
{
    bool textTruncated = false;

    mTextLayout.setText(text);

    qreal yLinePos = 0;
    mTextLayout.beginLayout();
    while (1) {
        QTextLine line = mTextLayout.createLine();
        if (!line.isValid())
            break;

        line.setLineWidth(lineWidth);
        line.setPosition(QPointF(0, yLinePos));

        if( ( mMaxLines > 0 ) && ( mTextLayout.lineCount() >= mMaxLines ) ) {
            textTruncated = (line.textStart()+line.textLength() < text.length());
            break;
        }
        yLinePos += lineSpacing;
    }
    mTextLayout.endLayout();

    return textTruncated;
}

void HbTextItemPrivate::rebuildTextLayout(const QSizeF &newSize)
{
    QFontMetricsF fontMetrics(mTextLayout.font());

    const qreal lineWidth = qRound( newSize.width() + 0.5 ); // round up to integer

    updateTextOption();

    QString tempText(mText);
    if(tempText.indexOf('\n')>=0) {
        // to prevent creation of deep copy if replace has no effect
        tempText.replace('\n', QChar::LineSeparator);
    }

	// Need to call elidedText explicitly to enable multiple length translations.
    tempText = fontMetrics.elidedText(tempText, Qt::ElideNone, lineWidth);
    bool textTruncated = doLayout(tempText, lineWidth, fontMetrics.lineSpacing());

    if(mElideMode!=Qt::ElideNone && !tempText.isEmpty()) {
        if( ( mTextLayout.boundingRect().height() - newSize.height() > EPSILON ) ||
            ( mTextLayout.boundingRect().width() - lineWidth > EPSILON ) ||
              textTruncated) {
            // TODO: Multiple length translations with multiline text
            doLayout(elideLayoutedText(newSize, fontMetrics),
                     lineWidth,
                     fontMetrics.lineSpacing());
        }
    }

    calculateVerticalOffset();
    calculateFadeRects();

    // build of text layout is completed
    mInvalidateShownText = false;
}

/*
    finds index of last line before given Y coordinate
    It is a binary search.
 */
int HbTextItemPrivate::findIndexOfLastLineBeforeY(qreal y) const
{
    int i=0,j=mTextLayout.lineCount();

    if( ( mMaxLines > 0 ) && ( mMaxLines < j ) ){
        j = mMaxLines;
    }

    while(i!=j) {
        int k = (i+j)>>1;
        if(mTextLayout.lineAt(k).naturalTextRect().bottom()>y) {
            j=k;
        } else {
            if(i==k) {
                break;
            }
            i=k;
        }
    }
    return i;
}

QSizeF HbTextItemPrivate::respectSizeLimits(QSizeF size) const
{
    QFontMetricsF metrics(mTextLayout.font());

    Q_ASSERT(mMinLines>0);
    qreal minHeight = metrics.lineSpacing()*mMinLines - metrics.leading();
    if (size.height()<minHeight) {
        size.setHeight(minHeight);
    }

    if (mMaxLines>0) {
        qreal maxHeight = metrics.lineSpacing()*mMaxLines - metrics.leading();
        if (size.height()>maxHeight) {
            size.setHeight(maxHeight);
        }
    }
    return size;
}

QString HbTextItemPrivate::elideLayoutedText(const QSizeF& size, const QFontMetricsF& metrics) const
{
    int lastVisibleLine =findIndexOfLastLineBeforeY(size.height());
    QTextLine lastLine = mTextLayout.lineAt(lastVisibleLine);

    // all visible lines without last visible line
    QString textToElide = mTextLayout.text();
    QString elidedText = textToElide.left(lastLine.textStart());

    if(!elidedText.isEmpty() && !elidedText.endsWith(QChar::LineSeparator)) {
        // needed to prevent to move "..." to line before last line
        elidedText.append(QChar::LineSeparator);
    }

    int n = lastLine.textLength();
    if(textToElide.at(lastLine.textStart()+n-1)!=QChar::LineSeparator) {
        n = -1;
    }
    elidedText.append(metrics.elidedText(textToElide.mid(lastLine.textStart(), n),
                                         mElideMode, size.width(),textFlagsFromTextOption()));

    return elidedText;
}

void HbTextItemPrivate::updateTextOption()
{
    Q_Q(HbTextItem);

    QTextOption textOpt = mTextLayout.textOption();
    textOpt.setAlignment(QStyle::visualAlignment(q->layoutDirection(), q->alignment()));
    textOpt.setTextDirection(q->layoutDirection());
    mTextLayout.setTextOption(textOpt);
}

void HbTextItemPrivate::calculateVerticalOffset()
{
    Q_Q(HbTextItem);

    mOffsetPos.setY(0);
    Qt::Alignment align = q->alignment();
    if(!align.testFlag(Qt::AlignTop) && (align & Qt::AlignVertical_Mask)!=0 ) {
        int index = mTextLayout.lineCount()-1;
        if(index>=0) {
            qreal diff = q->size().height();
            diff -= mTextLayout.lineAt(index).rect().bottom();
            if(align & Qt::AlignVCenter) {
                diff *=(qreal)0.5;
            }
            if(diff>=0 || mElideMode==Qt::ElideNone) {
                mOffsetPos.setY(diff);
            }
        }
    }
}

int HbTextItemPrivate::textFlagsFromTextOption() const
{
    QTextOption textOption = mTextLayout.textOption();
    int flags = (int)mAlignment;

    switch(textOption.wrapMode()) {
    case QTextOption::NoWrap:
        break;
    case QTextOption::WordWrap:
        flags |= Qt::TextWordWrap;
        break;
    case QTextOption::ManualWrap:
        break;
    case QTextOption::WrapAnywhere:
        flags |= Qt::TextWrapAnywhere;
        break;
    case QTextOption::WrapAtWordBoundaryOrAnywhere:
        flags |= Qt::TextWordWrap | Qt::TextWrapAnywhere;
        break;
    }

    if(q_ptr->flags().testFlag(QGraphicsItem::ItemClipsToShape)) {
        flags |= Qt::TextDontClip;
    }
    if(!q_ptr->isVisible()) { // or ItemHasNoContents?
        flags |= Qt::TextDontPrint;
    }

    return flags;
}

/*!
    This method check spetial case of calculating sizeHint.
    By default prefferedSize returns size of text which has lots of free space.
    But there can be situaltion that text has less avaible free width ther requred.
    In such cases prefered hight of HbTextItem should be recaculated because wrapping
    of the line may requre more space in vertical direction.

    In such case bigger hight should be cached for sizeHint calculations.
 */
bool HbTextItemPrivate::isAdjustHightNeeded(const QSizeF& newSize,
                                            const QSizeF& prefSize)
{
#ifdef HB_TEXT_ITEM_LOGS
    qDebug() << "isAdjustHightNeeded for: " << q_ptr->objectName()
            << " text=" << mText.left(20)
            << " adjusted=" << mAdjustedSize
            << " pref=" << prefSize
            << mTextLayout.font();
#endif // HB_TEXT_ITEM_LOGS

    // first check if wrapping of text is not active
    QTextOption::WrapMode wrapMode = mTextLayout.textOption().wrapMode();
    if (wrapMode==QTextOption::NoWrap
        || wrapMode==QTextOption::ManualWrap) {
        return false;
    }

    // check if line count is fixed
    if (mMaxLines == mMinLines) {
        return false;
    }

    // check if preffered height is defined by user
    //    so check if preff height is same as value returned by sizeHint
    if (!qFuzzyCompare(prefSize.height(), mAdjustedSize.height())) {
        return false;
    }

    // check if adjusted size has been already calculated
    // new width is bigger then last estimated range of same height
    if ((mMaxWidthForAdjust>=newSize.width()
        // new width is smaller then last estimated range of same height
        && newSize.width()>=mMinWidthForAdjust)) {
        return false;
    }

    if (!mAdjustedSize.isValid()) {
        // this means that preferred size is set outside of class by setPreferredSize
        // so sizeHint(Qt::Preferredsize) was not called and size adjustment is useless
        return false;
    }

    // if preconditions are met test if current hight is enough
    const QFontMetricsF metrics(mTextLayout.font());

    // heavy calculation: check if text fits in current size and cache result
    QSizeF newAdjust = metrics.boundingRect(QRectF(0, 0, newSize.width(), QWIDGETSIZE_MAX),
                                       textFlagsFromTextOption(),
                                       mText).size();

    if (qFuzzyCompare(newAdjust.height(), mAdjustedSize.height())) {
        // height is same as last time update range of same height
        mMaxWidthForAdjust = qMax(mMaxWidthForAdjust, newSize.width());
        mMinWidthForAdjust = qMin(mMinWidthForAdjust, newSize.width());

        // and don't update geometry
        return false;
    }

    // new height was calculated create new range for which
    // current mAdjustedSize.height is valid
    mMaxWidthForAdjust = newSize.width();
    mMinWidthForAdjust = newAdjust.width();

    // store new hieght use don't change width
    mAdjustedSize.setHeight(newAdjust.height());
    Q_ASSERT_X(mAdjustedSize.width()>=newAdjust.width(),
               "HbTextItemPrivate::isAdjustHightNeeded",
               QString("Fail for string: \"%1\"").arg(mText).toAscii().data());

    if (respectSizeLimits(mAdjustedSize)==prefSize) {
        // updateGeometry has no effect
        return false;
    }

    // all conditions for calling updateGeometry are meet
    return true;
}

void HbTextItemPrivate::clearAdjustedSizeCache()
{
    // clear cache of size
    mMinWidthForAdjust = -1;
    mMaxWidthForAdjust = -1;
    mAdjustedSize.setHeight(-1);
}

QSizeF HbTextItemPrivate::calculatePrefferedSize(const QSizeF& constraint) const
{
    const QFontMetricsF metrics(mTextLayout.font());

    if (mAdjustedSize.isValid() &&
        (constraint.height()<0 || qFuzzyCompare(constraint.height(), mAdjustedSize.height())) &&
        (constraint.width()<0  || qFuzzyCompare(constraint.width(), mAdjustedSize.width()))) {
        // return cached value, see more in:
        //      - HbTextItemPrivate::isAdjustHightNeeded
        //      - HbTextItem::resizeEvent
        return mAdjustedSize;
    }

    QSizeF maxSize(QWIDGETSIZE_MAX, QWIDGETSIZE_MAX);
    if(constraint.width()>0) {
        maxSize.setWidth(constraint.width());
    }
    if(constraint.height()>0) {
        maxSize.setHeight(constraint.height());
    }

    QSizeF size = metrics.boundingRect(QRectF(QPointF(),maxSize),
                                       textFlagsFromTextOption(),
                                       mText).size();

    mAdjustedSize = size;
    mDefaultHeight = size.height();
    mMinWidthForAdjust = size.width();
    mMaxWidthForAdjust = QWIDGETSIZE_MAX;

    return size;
}

bool HbTextItemPrivate::fadeNeeded(const QRectF& contentRect) const
{
    return (mFadeLengthX!=0 || mFadeLengthY!=0)
            && !contentRect.contains(
                    layoutBoundingRect().adjusted(KFadeTolerance,
                                                  KFadeTolerance,
                                                  -KFadeTolerance,
                                                  -KFadeTolerance));
}

void HbTextItemPrivate::setupGradient(QLinearGradient *gradient, QColor color)
{
    gradient->setColorAt(1.0, color);
    color.setAlpha(color.alpha()>>2); // 1/4 of initial opacity
    gradient->setColorAt(0.5, color); // middle color to improve feeling of fade effect
    color.setAlpha(0); // fully transparent
    gradient->setColorAt(0.0, color);
}

void HbTextItemPrivate::calculateFadeRects()
{
    Q_Q(const HbTextItem);

    const QRectF contentRect = q->contentsRect();
    mFadeToRect = contentRect;
    mFadeFromRect = contentRect;

    if(mFadeLengthX>0) {
        mFadeFromRect.moveLeft(mFadeLengthX);
        mFadeFromRect.setRight(contentRect.right()-mFadeLengthX);

        if(mFadeFromRect.width()<0) {
            mFadeFromRect.moveLeft(mFadeFromRect.center().x());
            mFadeFromRect.setWidth(0.0);
        }
    } else {
        mFadeToRect.moveLeft(mFadeLengthX);
        mFadeToRect.setRight(contentRect.right()-mFadeLengthX);
    }

    if(mFadeLengthY>0) { // TODO: alternative direction
        mFadeFromRect.moveTop(mFadeLengthY);
        mFadeFromRect.setBottom(contentRect.bottom()-mFadeLengthY);

        if(mFadeFromRect.height()<0) {
            mFadeFromRect.moveTop(mFadeFromRect.center().y());
            mFadeFromRect.setHeight(0.0);
        }
    } else {
        mFadeToRect.moveTop(mFadeLengthY);
        mFadeToRect.setBottom(contentRect.bottom()-mFadeLengthY);
    }

    qreal dx,dy;
    dx = mFadeFromRect.left() - mFadeToRect.left();
    dy = mFadeFromRect.top()  - mFadeToRect.top();
    if(dx!=0 || dy!=0) {
        // corner gradient vectors
        qreal scale = dx*dy/(dx*dx+dy*dy);
        mCornerFadeX = dy*scale;
        mCornerFadeY = dx*scale;
    } else {
        mCornerFadeX = 1;
        mCornerFadeY = 0;
    }

    mPaintFaded = fadeNeeded(contentRect);
}

/*
    This work-around is needed since there is a problem with pen transformations
    in hardware Open VG renderer. This problem occurs only on s60 hardware.
    On platforms: Linux, Windows and S60 emulator there is no such problem.
    Below flag detects platform which have this problem to activate work-around.
 */
#if defined(Q_WS_S60) && defined(Q_BIG_ENDIAN)
#   warning Work-around is active in fade effect of HbTextItem (see comment)
#   define HB_FADE_EFFECT_WORKAROUND_ON_PHONE
#endif
inline void HbTextItemPrivate::setPainterPen(QPainter *painter,
                         const QPen& pen,
                         const QPointF& lineBegin)
{
#ifdef HB_FADE_EFFECT_WORKAROUND_ON_PHONE
    const QGradient *gradient = pen.brush().gradient();
    if (!gradient || gradient->type()!=QGradient::LinearGradient) {
        painter->setPen(pen);
        return;
    }
    const QLinearGradient* linGrad = static_cast<const QLinearGradient*>(gradient);
    QLinearGradient newGrad(*linGrad);
    newGrad.setStart(newGrad.start()-lineBegin);
    newGrad.setFinalStop(newGrad.finalStop()-lineBegin);

    QBrush newBrush(newGrad);
    QPen newPen;
    newPen.setBrush(newBrush);
    painter->setPen(newPen);
#else
    Q_UNUSED(painter)
    Q_UNUSED(pen)
    Q_UNUSED(lineBegin)
#endif // HB_FADE_EFFECT_WORKAROUND_ON_PHONE
}

/*
    This method paints single piece of text layout.
    If line contains criticalX value then fadePen is used for painting in other
    case normalPen is used.
 */
void HbTextItemPrivate::paintArea(QPainter *painter,
                                  int firstItemToPaint,
                                  int lastItemToPaint,
                                  const QPen& normalPen,
                                  const QPen& fadePen,
                                  qreal criticalX) const
{
    for(int i=firstItemToPaint; i<=lastItemToPaint; ++i) {
        QTextLine line = mTextLayout.lineAt(i);
        QRectF lineRect = line.naturalTextRect();
        lineRect.translate(mOffsetPos);

#ifdef HB_FADE_EFFECT_WORKAROUND_ON_PHONE
        const QPointF gradientOffset(lineRect.left(),
                                     lineRect.top()+line.ascent());

        if (lineRect.left()<criticalX && lineRect.right()>criticalX) {
            setPainterPen(painter, fadePen, gradientOffset);
        } else {
            setPainterPen(painter, normalPen, gradientOffset);
        }
#else
        if (lineRect.left()<criticalX && lineRect.right()>criticalX) {
            painter->setPen(fadePen);
        } else {
            painter->setPen(normalPen);
        }
#endif // HB_FADE_EFFECT_WORKAROUND_ON_PHONE

        line.draw(painter, mOffsetPos);
    } // for i
}

/*
    This method is used to draw center part of lines.
    It is also used to calculate range of lines needed for painting in this range.
 */
int HbTextItemPrivate::paintArea(QPainter *painter,
              int firstItemToPaint,
              const QPen& normalPen,
              qreal lastValidY) const
{
    int i;
    const int n = mTextLayout.lineCount();

#ifndef HB_FADE_EFFECT_WORKAROUND_ON_PHONE
    painter->setPen(normalPen);
#endif

    for(i=firstItemToPaint; i<n; ++i) {
        QTextLine line = mTextLayout.lineAt(i);
        QRectF lineRect = line.naturalTextRect();
        lineRect.translate(mOffsetPos);

        if (lineRect.top()>lastValidY) {
            return i;
        }
#ifdef HB_FADE_EFFECT_WORKAROUND_ON_PHONE
        const QPointF gradientOffset(lineRect.left(),
                                     lineRect.top()+line.ascent());

        setPainterPen(painter, normalPen, gradientOffset);
#endif // HB_FADE_EFFECT_WORKAROUND_ON_PHONE

        line.draw(painter, mOffsetPos);

        if (lineRect.bottom()>lastValidY) {
            return i;
        }
    } // for i
    return n-1;
}

bool HbTextItemPrivate::setClipPath(QPainter *painter,
                                    const QRectF& rect,
                                    const QPainterPath& initialCliping) const
{
    if (initialCliping.isEmpty()) {
        painter->setClipRect(rect);
    } else {
        QPainterPath newPath(rect.topLeft());
        newPath.addRect(rect);

        if (!initialCliping.intersects(newPath)) {
            return false; // dont paint
        }
        newPath = initialCliping.intersected(newPath);

        painter->setClipPath(newPath);
    }
    return true;
}

void HbTextItemPrivate::paintWithFadeEffect(QPainter *painter) const
{
    Q_Q(const HbTextItem);

    QLinearGradient gradient;
    setupGradient(&gradient, q->textColor());
    const QPainterPath initialClipPath = painter->clipPath();

    const QRectF contentRect = q->contentsRect();
    int i=0;

    const int n = mTextLayout.lineCount();

// #define SEE_FADE_RECTANGLES
#ifdef SEE_FADE_RECTANGLES
    painter->setBrush(QBrush(QColor(215, 0, 0, 30)));
    painter->drawRect(mFadeToRect);
    painter->setBrush(QBrush(QColor(0, 0, 200, 30)));
    painter->drawRect(mFadeFromRect.adjusted(0,0,-1,-1));
#endif // SEE_FADE_RECTANGLES

    QRectF centerRect(mFadeToRect);
    if(mTextLayout.lineAt(0).y()+mOffsetPos.y()<contentRect.top()) {
        centerRect.setTop(mFadeFromRect.top());

        // top center gradient (==):
        QPointF from(mFadeFromRect.topLeft());
        gradient.setStart(mFadeFromRect.left(),mFadeToRect.top());
        gradient.setFinalStop(from);
        QBrush centerBrush(gradient);
        QPen centerPen;
        centerPen.setBrush(centerBrush);

        if (setClipPath(painter,
                        QRectF(gradient.start(), mFadeFromRect.topRight()),
                        initialClipPath)) {
            i = paintArea(painter, 0, centerPen, from.y());
        } else {
            i = 0;
        }

        // top left gradient (//):
        gradient.setStart(from.x()-mCornerFadeX, from.y()-mCornerFadeY);
        QBrush leftBrush(gradient);
        QPen leftPen;
        leftPen.setBrush(leftBrush);
        if (setClipPath(painter,
                        QRectF(mFadeToRect.topLeft(), mFadeFromRect.topLeft()),
                        initialClipPath)) {
            paintArea(painter, 0, i, centerPen, leftPen, contentRect.left()-KFadeTolerance);
        }

        // top right gradient (\\):
        from = mFadeFromRect.topRight();
        gradient.setStart(from.x()+mCornerFadeX, from.y()-mCornerFadeY);
        gradient.setFinalStop(from);
        QBrush rightBrush(gradient);
        QPen rightPen;
        rightPen.setBrush(rightBrush);

        if (setClipPath(painter,
                        QRectF(mFadeToRect.topRight(), mFadeFromRect.topRight()),
                        initialClipPath)) {
            paintArea(painter, 0, i, centerPen, rightPen, contentRect.right()+KFadeTolerance);
        }
    }

    bool paintBottom = false;
    if(mTextLayout.lineAt(n-1).naturalTextRect().bottom()+mOffsetPos.y()>contentRect.bottom()) {
        // bottom fade is needed here
        centerRect.setBottom(mFadeFromRect.bottom());
        paintBottom = true;
    }

    // paint center part
    {
        int startFrom = i;
        QPen centerPen(q->textColor());
        if (setClipPath(painter,
                        QRectF(QPointF(mFadeFromRect.left(), centerRect.top()),
                               QPointF(mFadeFromRect.right(), centerRect.bottom())),
                        initialClipPath)) {
            // center with no gradient:
            i = paintArea(painter, i, centerPen, centerRect.bottom());
        }

        // left gradient | ||
        gradient.setStart(mFadeToRect.left(), mFadeFromRect.top());
        gradient.setFinalStop(mFadeFromRect.topLeft());
        QBrush leftBrush(gradient);
        QPen leftPen;
        leftPen.setBrush(leftBrush);
        if (setClipPath(painter,
                        QRectF(centerRect.topLeft(),
                               QPointF(mFadeFromRect.left(),
                                       centerRect.bottom())),
                        initialClipPath)) {
            paintArea(painter, startFrom, i, centerPen, leftPen, contentRect.left()-KFadeTolerance);
        }

        // right gradient || |
        gradient.setStart(mFadeToRect.right(), mFadeFromRect.top());
        gradient.setFinalStop(mFadeFromRect.topRight());
        QBrush rightBrush(gradient);
        QPen rightPen;
        rightPen.setBrush(rightBrush);

        if (setClipPath(painter,
                        QRectF(QPointF(mFadeFromRect.right(), centerRect.top()),
                               centerRect.bottomRight()),
                        initialClipPath)) {
            paintArea(painter, startFrom, i, centerPen, rightPen, contentRect.right()+KFadeTolerance);
        }
    }

    // need to draw bottom as faded? is some lines remained?
    if (paintBottom) {
        int startFrom = i;

        // bottom center gradient (==):
        QPointF from(mFadeFromRect.bottomLeft());
        gradient.setFinalStop(from);
        gradient.setStart(mFadeFromRect.left(),mFadeToRect.bottom());
        QBrush centerBrush(gradient);
        QPen centerPen;
        centerPen.setBrush(centerBrush);

        if (setClipPath(painter,
                        QRectF(mFadeFromRect.bottomLeft(),
                               QPointF(mFadeFromRect.right(), mFadeToRect.bottom())),
                        initialClipPath)) {
            // center with no gradient:
            i = paintArea(painter, i, centerPen, mFadeToRect.bottom());
        }

        // bottom left gradient (\\):
        gradient.setStart(from.x()-mCornerFadeX, from.y()+mCornerFadeY);
        QBrush leftBrush(gradient);
        QPen leftPen;
        leftPen.setBrush(leftBrush);

        if (setClipPath(painter,
                        QRectF(mFadeFromRect.bottomLeft(),
                               mFadeToRect.bottomLeft()),
                        initialClipPath)) {
            // center with no gradient:
            paintArea(painter, startFrom, i, centerPen, leftPen, contentRect.left()-KFadeTolerance);
        }

        // bottom right gradient (//):
        from = mFadeFromRect.bottomRight();
        gradient.setStart(from.x()+mCornerFadeX, from.y()+mCornerFadeY);
        gradient.setFinalStop(from);
        QBrush rightBrush(gradient);
        QPen rightPen;
        rightPen.setBrush(rightBrush);

        if (setClipPath(painter,
                        QRectF(mFadeFromRect.bottomRight(),
                               mFadeToRect.bottomRight()),
                        initialClipPath)) {
            // center with no gradient:
            paintArea(painter, startFrom, i, centerPen, rightPen, contentRect.right()+KFadeTolerance);
        }
    }

    // restoring initial clipping region
    painter->setClipPath(initialClipPath);
}

void HbTextItemPrivate::setFadeLengths(qreal xLength, qreal yLength)
{
    static const qreal KMinDiff = 0.5;
    Q_Q( HbTextItem );

    if(qAbs(mFadeLengthX - xLength)>KMinDiff
       || qAbs(mFadeLengthY - yLength)>KMinDiff) {
        if(mFadeLengthX<0 || xLength<0
           || mFadeLengthY<0 || yLength<0) {
            // in this cases boundingRect will be changed
            q->prepareGeometryChange();
        }
        mFadeLengthX = (qAbs(xLength)<=KMinDiff)? 0.0: xLength;
        mFadeLengthY = (qAbs(yLength)<=KMinDiff)? 0.0: yLength;

        calculateFadeRects();

        q->update();
    }
}

QRectF HbTextItemPrivate::layoutBoundingRect () const
{
    QRectF result;
    for (int i=0, n=mTextLayout.lineCount(); i<n; ++i) {
        result = result.unite(
                mTextLayout.lineAt(i).naturalTextRect());
    }

    result.translate(mOffsetPos);

    return result;
}

QRectF HbTextItemPrivate::boundingRect (const QRectF& contentsRect) const
{
    QRectF result(layoutBoundingRect());
    if(q_ptr->flags().testFlag(QGraphicsItem::ItemClipsToShape)) {
        // clip
        QRectF clippedTo = contentsRect;

        qreal dx = qMin(mFadeLengthX, (qreal)0.0);
        qreal dy = qMin(mFadeLengthY, (qreal)0.0);
        clippedTo.adjust(dx, dy, -dx, -dy);

        result = result.intersected(clippedTo);
    }

    if (HbTextItemPrivate::outlinesEnabled) {
        result = result.united(contentsRect);
    }

    return result;
}

void HbTextItemPrivate::scheduleTextBuild()
{
    mInvalidateShownText = true;
}

/*!
    @alpha
    @hbcore
    \class HbTextItem
    \brief HbTextItem is a lightweight item for showing text.


    This is mainly used as a primitive in widgets.
    It derives from HbWidgetBase so it can be layouted.
 */

/*!
    Constructor for the class with no content.
 */
HbTextItem::HbTextItem (QGraphicsItem *parent) :
    HbWidgetBase(*new HbTextItemPrivate, parent)
{
    Q_D(HbTextItem);
    d->init(parent);
}


/*!
    Constructs object with a \a text content.
 */
HbTextItem::HbTextItem (const QString &text, QGraphicsItem *parent) :
    HbWidgetBase(*new HbTextItemPrivate, parent)
{
    Q_D(HbTextItem);
    d->init(parent);
    setText(text);
}

/*
    Constructor for internal use only
 */
HbTextItem::HbTextItem (HbTextItemPrivate &dd, QGraphicsItem * parent) :
    HbWidgetBase(dd, parent)
{
    Q_D(HbTextItem);
    d->init(parent);
}

/*!
    Destructor for the class.
 */
HbTextItem::~HbTextItem ()
{
}

/*!
    Returns the text shown by object.

    \sa HbTextItem::setText()
 */
QString HbTextItem::text () const
{
    Q_D( const HbTextItem );
    return d->mText;
}

/*!
    Returns the text color used for painting text.
    If no color was set it returns color based on theme.

    \sa HbTextItem::setTextColor()
 */
QColor HbTextItem::textColor () const
{
    Q_D( const HbTextItem );

    if (d->mColor.isValid()) { // Means user has set text color
        return d->mColor;
    }
    if (!d->mDefaultColor.isValid()) {
        d->mDefaultColor = HbColorScheme::color(KDefaultColorThemeName);
    }
    return d->mDefaultColor;
}


/*!
    Returns the text alignment. It supports vertical and horizontal alignment.

    \sa HbTextItem::setAlignment()
 */
Qt::Alignment HbTextItem::alignment () const
{
    Q_D( const HbTextItem );
    return d->mAlignment;
}

/*!
    Returns the elide mode of the text.
    This option decide how last line of text is truncated.

    \sa HbTextItem::setElideMode()
 */
Qt::TextElideMode HbTextItem::elideMode () const
{
    Q_D( const HbTextItem );
    return d->mElideMode;
}

/*!
    Sets the text into \a text.
 */
void HbTextItem::setText (const QString &text)
{
    Q_D(HbTextItem);

    QString txt( text );

#ifdef HB_TEXT_MEASUREMENT_UTILITY

    if ( HbFeatureManager::instance()->featureStatus( HbFeatureManager::TextMeasurement ) ) {
        if (text.endsWith(QChar(LOC_TEST_END))) {
            int index = text.indexOf(QChar(LOC_TEST_START));
            setProperty( HbTextMeasurementUtilityNameSpace::textIdPropertyName,  text.mid(index + 1, text.indexOf(QChar(LOC_TEST_END)) - index - 1) );
            setProperty( HbTextMeasurementUtilityNameSpace::textMaxLines, d->mMaxLines );
            txt = text.left(index);
        } else {
            setProperty( HbTextMeasurementUtilityNameSpace::textIdPropertyName,  QVariant::Invalid );
        }
    }
#endif //HB_TEXT_MEASUREMENT_UTILITY

    if (d->mText != txt) {
        d->scheduleTextBuild();
        prepareGeometryChange();
        d->mText = txt;
        d->mTextLayout.setCacheEnabled(KLayoutCacheLimit >= d->mText.length());
        d->clearAdjustedSizeCache();
        update();

        // check if call of updateGeometry can be ignored
        // don't call it when minimum and maximum lines are equal (height is fixed) or ...
        if ((d->mMinLines == d->mMaxLines)
            // or when preferred height is ignored
            || (sizePolicy().verticalPolicy()&QSizePolicy::IgnoreFlag)) {

            // and when preferred width is ignored or ...
            if (sizePolicy().horizontalPolicy()&QSizePolicy::IgnoreFlag
                // or when preferred width is defined from outside
                || !qFuzzyCompare(preferredWidth(), d->mAdjustedSize.width())) {
                  // TODO: looking for better solution since preferredWidth() can cause call of sizeHint

                // in those cases skip updateGeometry
                return;
            }
        }
        updateGeometry();
    }
}

/*!
    Sets the text color into \a color.
    If invalid color is used color from theme will be used.

    \sa HbTextItem::textColor()
 */
void HbTextItem::setTextColor (const QColor &color)
{
    Q_D(HbTextItem);

    d->setApiProtectionFlag(HbWidgetBasePrivate::AC_TextColor, color.isValid());
    if (d->mColor != color) {
        d->mColor = color;

        if (!color.isValid()) {
            QGraphicsWidget* cssHandler = parentWidget();
            // check if there is a widget which handles CSS
            if (cssHandler!=NULL) {
                // this is needed to enforce color fetch from CSS
                HbEvent themeEvent(HbEvent::ThemeChanged);
                QApplication::sendEvent(cssHandler, &themeEvent);
            }
        }

        if (!d->mText.isEmpty()) {
            update();
        }
    }
}

/*!
    Sets the text alignment into \a alignment.
    It supports vertical and horizontal alignment.

    \sa HbTextItem::alignment()
 */
void HbTextItem::setAlignment (Qt::Alignment alignment)
{
    Q_D(HbTextItem);
	d->setApiProtectionFlag(HbWidgetBasePrivate::AC_TextAlign, true);
    alignment &= Qt::AlignVertical_Mask | Qt::AlignHorizontal_Mask;
    if (d->mAlignment != alignment) {
        prepareGeometryChange();
        d->mAlignment = alignment;
        d->updateTextOption();
        d->calculateVerticalOffset();

        update();
    }
}

/*!
    Sets the elide mode into \a elideMode.
    The elide mode determines the truncation of the last line of text
    i.e. the "..." usage

    \sa HbTextItem::elideMode()
 */
void HbTextItem::setElideMode (Qt::TextElideMode elideMode)
{
    Q_D(HbTextItem);
    if (elideMode != d->mElideMode) {
        d->mElideMode = elideMode;
        d->scheduleTextBuild();
        prepareGeometryChange();
        update();
    }
}

/*!
    \reimp

    Paints text
 */
void HbTextItem::paint(QPainter *painter, const QStyleOptionGraphicsItem *option, QWidget *widget)
{
    Q_D(HbTextItem);
    Q_UNUSED(option);
    Q_UNUSED(widget);

    // optimalization:
    if (option->exposedRect.isEmpty()) {
        return;
    }

    // Save painter's state
    QPen oldPen = painter->pen();


    if (HbTextItemPrivate::outlinesEnabled){
        painter->setBrush(QBrush(QColor(255, 0, 0, 50)));
        QRectF rect(contentsRect());
        // to see border - bounding rect was clipping bottom and right border
        rect.adjust(0, 0, -1.0, -1.0);
        painter->drawRect(rect);
    }

    if (d->mInvalidateShownText) {
        d->rebuildTextLayout(size());
    }


    painter->setPen(textColor());

    Q_ASSERT(d->mPaintFaded == d->fadeNeeded(contentsRect()));
    if(d->mPaintFaded ) {
        d->paintWithFadeEffect(painter);
    } else {
        d->mTextLayout.draw(painter,
                            d->mOffsetPos,
                            QVector<QTextLayout::FormatRange>(),
                            flags().testFlag(ItemClipsToShape)?contentsRect():QRectF());
    }


    // Restore painter's state
    painter->setPen(oldPen);
}

/*!
    \reimp

    Sets geometry of text
 */
void HbTextItem::setGeometry(const QRectF & rect)
{
    HbWidgetBase::setGeometry(rect);

    if (parentLayoutItem() && parentLayoutItem()->isLayout()) {
        // rect.size can't be used here since size can be limited inside of
        // called method HbWidgetBase::setGeometry(rect) so size is used which
        // holds current size
        Q_D(HbTextItem);
        if (d->isAdjustHightNeeded(size(), preferredSize())) {
            updateGeometry();
        }
    }
}

/*!
    \reimp

    bounding rectangle.
 */
QRectF HbTextItem::boundingRect () const
{
    Q_D(const HbTextItem);

    if (d->mInvalidateShownText) {
        const_cast<HbTextItemPrivate*>(d)->rebuildTextLayout(size());
    }
    return d->boundingRect(contentsRect());
} // boundingRect()

/*!
    \reimp
 */
QSizeF HbTextItem::sizeHint(Qt::SizeHint which, const QSizeF &constraint) const
{
    Q_D(const HbTextItem);

    QSizeF size(0,0);

    // TODO: Temporary work-around - font change event are not always received
    // so updating font here (this is needed because of sizeHint adjustments).
    if (d->mTextLayout.font()!=font()) {
#ifdef HB_TEXT_ITEM_LOGS
        qWarning() << "Font change was not recieved on time: work-around is active"
                << objectName()
                << " test: " << d->mText.left(20)
                << " oldFont:" << d->mTextLayout.font()
                << " newFont:" << font();
#endif // HB_TEXT_ITEM_LOGS

        const_cast<HbTextItemPrivate *>(d)->mTextLayout.setFont(font());
        const_cast<HbTextItemPrivate *>(d)->clearAdjustedSizeCache();
    }

    Qt::Orientations effectiveOrientations(0);
    if ( !(sizePolicy().horizontalPolicy()&QSizePolicy::IgnoreFlag) ) {
        effectiveOrientations |= Qt::Horizontal;
    }

    if ( !(sizePolicy().verticalPolicy()&QSizePolicy::IgnoreFlag) ) {
        effectiveOrientations |= Qt::Vertical;
    }   

    if ( !effectiveOrientations ) {
        // if the whole sizeHint is ignored, return ASAP with default values (0<50<QMAX)
        return HbWidgetBase::sizeHint( which, constraint );
    }

    switch(which) {
    case Qt::MinimumSize: 
        {
            if ( !d->mText.isEmpty() ) {
                size.setWidth(MinimumWidth); // just to show something  -- should not matter in read use-case
                size = d->respectSizeLimits(size);
            }
            break;
        }

    case Qt::PreferredSize: 
        {
            if ( !effectiveOrientations.testFlag(Qt::Horizontal)
                    && (d->mMinLines == d->mMaxLines) ) {
                //optimize single line if the horizontal sizeHint is ignored
                size = d->respectSizeLimits(size);
                break;
            }

            size = d->calculatePrefferedSize(constraint);
            size = d->respectSizeLimits(size);
            break;
        }

    default:
        size = HbWidgetBase::sizeHint(which, constraint);
    }

    return size;
}

 /*!
    \reimp

    Detects: font changes, layout direction changes and theme changes.
 */
void HbTextItem::changeEvent(QEvent *event)
{
    // Listens theme changed event so that item size hint is

    switch(event->type()) {
    case QEvent::LayoutDirectionChange: {
            Q_D(HbTextItem);
            d->scheduleTextBuild();
        }
        break;

    case QEvent::FontChange: {
            Q_D(HbTextItem);

            if (!d->mTextLayout.font().isCopyOf(font())) {

#ifdef HB_TEXT_ITEM_LOGS
                qDebug() << "fontChangeEvent: " << objectName()
                        << " text: " << text().left(20)
                        << " font: " << font();
#endif // HB_TEXT_ITEM_LOGS

                d->mTextLayout.setFont(font());
                d->clearAdjustedSizeCache();
                d->scheduleTextBuild();
                prepareGeometryChange();
                updateGeometry();
            } else {
                // ignoring event since it has no effect
                return;
            }
        }
        break;

    default:
        // comparing event->type() with dynamic values:

        if (event->type() == HbEvent::ThemeChanged) {
            Q_D(HbTextItem);
            d->mDefaultColor = QColor(); 
            if(!d->mColor.isValid()) {
                update();
            }
        }
    }
    HbWidgetBase::changeEvent( event );
}

/*!
    \reimp
 */
void HbTextItem::resizeEvent (QGraphicsSceneResizeEvent *event)
{
    Q_D(HbTextItem);

    HbWidgetBase::resizeEvent(event);

    d->scheduleTextBuild();
}

/*!
    @proto
    Sets style of text wrapping. \a mode type will be changed to Hb::TextWrapping
    after appropriate merge.

    \sa HbTextItem::textWrapping
    \sa QTextOption::setWrapMode
 */
void HbTextItem::setTextWrapping(Hb::TextWrapping mode)
{
    Q_D(HbTextItem);
	d->setApiProtectionFlag(HbWidgetBasePrivate::AC_TextWrapMode, true);
    QTextOption::WrapMode textWrapMode = static_cast<QTextOption::WrapMode>(mode);

    QTextOption textOption = d->mTextLayout.textOption();
    if(textOption.wrapMode()!=textWrapMode) {
        textOption.setWrapMode(textWrapMode);
        d->mTextLayout.setTextOption(textOption);
        if(!d->mText.isEmpty()) {
            d->scheduleTextBuild();
            prepareGeometryChange();
            update();
        }

        // is size hint adjustable?
        if (parentLayoutItem() && parentLayoutItem()->isLayout()) {
            if (d->mAdjustedSize.isValid() &&
                d->mAdjustedSize.width() > size().width()) {
                // restore default size hint
                d->mAdjustedSize = d->respectSizeLimits(
                        QSizeF(d->mAdjustedSize.width(), d->mDefaultHeight));
                d->mMinWidthForAdjust = d->mAdjustedSize.width();
                d->mMaxWidthForAdjust = QWIDGETSIZE_MAX;

                updateGeometry();
            }
        }
    }
}

/*!
    @proto
    returns style of text wrapping.

    \sa HbTextItem::setTextWrapping
    \sa QTextOption::wrapMode
 */
Hb::TextWrapping HbTextItem::textWrapping() const
{
    Q_D(const HbTextItem);
    return static_cast<Hb::TextWrapping>(d->mTextLayout.textOption().wrapMode());
}

/*!
    Shows (default) or hides text.
    Size hint remains unchanged (same as when text is visible).

    Equvalent of QGraphicsItem::setVisible(bool)
 */
void HbTextItem::setTextVisible(bool isVisible)
{
    setVisible(isVisible);
}

/*!
    Returns if text is visible.

    \sa HbTextItem::setTextVisible(bool)

    Equvalent of QGraphicsItem::isVisible()
 */
bool HbTextItem::isTextVisible() const
{
    return isVisible();
}

/*!
    enables (default) od disables text cliping when item geometry is to small.

    \sa HbTextItem::isTextClip()

    Equvalent of QGraphicsItem::setFlag(QGraphicsItem::ItemClipsToShape, clipping)
 */
void HbTextItem::setTextClip(bool clipping)
{
    setFlag(QGraphicsItem::ItemClipsToShape, clipping);
}

/*!
    Returns true if text is clipped when item geometry is too small.

    \sa HbTextItem::setTextClip(bool)

    Equvalent of QGraphicsItem::flags().testFlag(QGraphicsItem::ItemClipsToShape)
 */
bool HbTextItem::isTextClip() const
{
    return flags().testFlag(ItemClipsToShape);
}

/*!
    Sets minimum number of lines for text item. If minimum number of lines is set,
    then text item will always draw at least this number of lines.

    If you set minimum lines bigger than maximum lines, then maximum lines parameter
    will be automatically increased.

    Pass negative or zero value as an input parameter to unset this constraint

    \sa HbTextItem::minimumLines()
    \sa HbTextItem::setMaximumLines()
    \sa HbTextItem::maximumLines()
 */
void HbTextItem::setMinimumLines( int minLines )
{
    Q_D( HbTextItem );
    minLines = qMax(minLines, 1); // zero or nagative values are meanless and are restoring 1

	d->setApiProtectionFlag(HbWidgetBasePrivate::AC_TextLinesMin, true);

    if( minLines != d->mMinLines ) {
        if( ( d->mMaxLines > 0 ) && ( minLines > d->mMaxLines ) ) {
            d->mMaxLines = minLines;
        }
        d->mMinLines = minLines;

        // not needed?: d->clearAdjustedSizeCache(); // some condition?
        updateGeometry();
    }
}

/*!
    Sets maximum number of lines for text item. If maximum number of lines is set,
    then text item will not draw more lines then this maximum.

    Pass negative or zero value as an input parameter to unset this constraint

    If you set maximum lines less than minimum lines, then minimum lines parameter
    will be automatically decreased.

    \sa HbTextItem::maximumLines()
    \sa HbTextItem::setMinimumLines()
    \sa HbTextItem::minimumLines()
 */
void HbTextItem::setMaximumLines( int maxLines )
{
    Q_D( HbTextItem );
	d->setApiProtectionFlag(HbWidgetBasePrivate::AC_TextLinesMax, true);

    maxLines = qMax(maxLines, 0);

    if( maxLines != d->mMaxLines ) {
        if ((maxLines > 0) && (maxLines < d->mMinLines)){
            d->mMinLines = maxLines;
        }
        d->mMaxLines = maxLines;

        d->scheduleTextBuild();
        prepareGeometryChange();
        update();

        updateGeometry();
#ifdef HB_TEXT_MEASUREMENT_UTILITY
        if ( HbFeatureManager::instance()->featureStatus( HbFeatureManager::TextMeasurement ) ) {
            setProperty( HbTextMeasurementUtilityNameSpace::textMaxLines, d->mMaxLines );
        }
#endif
    }
}

/*!
    \sa HbTextItem::setMinimumLines()
    \sa HbTextItem::setMaximumLines()
    \sa HbTextItem::maximumLines()
    \return "minimum lines" parameter (zero value means unset)
 */
int HbTextItem::minimumLines() const
{
    Q_D( const HbTextItem );
    return d->mMinLines;
}

/*!
    \sa HbTextItem::setMaximumLines()
    \sa HbTextItem::setMinimumLines()
    \sa HbTextItem::minimumLines()
    \return "maximum lines" parameter
 */
int HbTextItem::maximumLines() const
{
    Q_D( const HbTextItem );
    return d->mMaxLines;
}

/*!
    @proto

    returns distance which text fades out when reaching border of item.

    \sa HbTextItem::setFadeLengths(qreal, qreal)
*/
QPointF HbTextItem::fadeLengths() const
{
    Q_D( const HbTextItem );
    return QPointF(d->mFadeLengthX, d->mFadeLengthY);
}

/*!
    @proto

    Method provided for convenience.
    Equivalent of setFadeLengths(length, length).

    \sa HbTextItem::setFadeLengths(qreal, qreal)
*/
void HbTextItem::setFadeLength(qreal length)
{
    Q_D( HbTextItem );
    d->setFadeLengths(length, length);
}

/*!
    @proto

    Sets distance on which text will be fade out when reaching border of item.

    Effect is performed only when text should be clipped at specified border.

    Positive value means that fade will end at border of contentsRect()
    and will start at a \a length distance inside of this rectangle.

    Zero value disables the feature.

    Behavior for negative values is undefined.

    Note that text clip (setTextClip) must be set to true to use this effect.

    xLength and yLength values refer to fade effect to horizontal and vertical
    direction respectively.
 */
void HbTextItem::setFadeLengths(qreal xLength, qreal yLength)
{
    Q_D( HbTextItem );
    d->setFadeLengths(xLength, yLength);
}

/*!
    @proto

    Method provided for connivance.
    Equivalent of setFadeLengths(lengths.x(), lengths.y()).

    \sa HbTextItem::setFadeLengths(qreal, qreal)
 */
void HbTextItem::setFadeLengths(const QPointF& lengths)
{
    Q_D( HbTextItem );
    d->setFadeLengths(lengths.x(), lengths.y());
}

// end of file