/****************************************************************************
**
** 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_p.h"
#endif

#define EPSILON 0.01

bool HbTextItemPrivate::outlinesEnabled = false;

static const QString KDefaultColorThemeName = "qtc_view_normal";
const int MinimumWidth = 5; // 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),
    mDontPrint(false),
    mDontClip(false),
    mInvalidateShownText(true),
    mOffsetPos(0,0),
    mPaintFaded(false),
    mFadeLengthX(30),
    mFadeLengthY(15),
    mPrefHeight(0),
    mMinLines(0),
    mMaxLines(0),
    mNeedToAdjustSizeHint(false),
    mUpdateColor(true)
{
}

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

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

    QTextOption textOption = mTextLayout.textOption();
    textOption.setWrapMode(QTextOption::WordWrap);
    mTextLayout.setTextOption(textOption);
    mTextLayout.setCacheEnabled(true);
}

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

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

    mTextLayout.setText(text);
    mTextLayout.setFont( q_func()->font() );

    qreal height = 0;
    mTextLayout.beginLayout();
    while (1) {
        QTextLine line = mTextLayout.createLine();
        if (!line.isValid())
            break;
        if( ( mMaxLines > 0 ) && ( mTextLayout.lineCount() > mMaxLines ) ) {
            textTruncated = true;
            break;
        }

        line.setLineWidth(lineWidth);
        height += leading;
        line.setPosition(QPointF(0, height));
        height += line.height();
    }
    mTextLayout.endLayout();

    if( textTruncated ) {
        mTextLayout.setText(text);
        mTextLayout.setFont( q_func()->font() );

        qreal height = 0;
        mTextLayout.beginLayout();
        while ( mTextLayout.lineCount() < mMaxLines ) {
            QTextLine line = mTextLayout.createLine();
            line.setLineWidth(lineWidth);
            height += leading;
            line.setPosition(QPointF(0, height));
            height += line.height();
        }
        mTextLayout.endLayout();
    }

    return textTruncated;
}

void HbTextItemPrivate::setSize(const QSizeF &newSize)
{
    Q_Q(HbTextItem);

    QFont usedFont = q->font();
    QFontMetricsF fontMetrics(usedFont);

    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);
    }

    // function does the layout only when needed
    mTextLayout.setFont(usedFont);
	// Need to call elidedText explicitly to enable multiple length translations.
    tempText = fontMetrics.elidedText(tempText, Qt::ElideNone, lineWidth);
    bool textTruncated = doLayout(tempText, lineWidth, fontMetrics.leading());
    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.leading());
        }
    }
    calculateVerticalOffset();
    calculateFadeRects();
    q->update();
}

/*
    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;
}

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(mDontClip)  flags |= Qt::TextDontClip;
    if(mDontPrint) flags |= Qt::TextDontPrint;

    return flags;
}

bool HbTextItemPrivate::adjustSizeHint()
{
    Q_Q( HbTextItem );

    mNeedToAdjustSizeHint = false;

    if ( !(q->sizePolicy().verticalPolicy()&QSizePolicy::IgnoreFlag) ) {
        // only calculated if the vertical sizeHint is taken into account

        const QFontMetricsF metrics(q->font());

        if ( mMinLines > 0 && (mMinLines == mMaxLines) ) {
            // if the number of lines if fixed: optimize
            const qreal newPrefHeight = ( metrics.height() + metrics.leading() ) * mMinLines - metrics.leading();
            if( qAbs( mPrefHeight - newPrefHeight ) > EPSILON ) {
                mPrefHeight = newPrefHeight;
                return true;
            }
            return false;
        }

        QSizeF currSize = q->size();
        // do the heavy calculation
        QRectF desiredRect = metrics.boundingRect( QRectF( 0, 0 , currSize.width(), QWIDGETSIZE_MAX ), textFlagsFromTextOption(), mText );

        if( qAbs( desiredRect.height() - mPrefHeight ) > EPSILON ) {
            mPrefHeight = desiredRect.height();
            return true;
        }
    }

    return false;
}

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 paint each line in tree pieces.
    In each piece uses different pen.
    When fade effect is not needed on some end centerPen is used.
 */
int HbTextItemPrivate::paintFaded(QPainter *painter,
                                  int firstItemToPaint,
                                  const QPen& leftPen,
                                  const QPen& centerPen,
                                  const QPen& rightPen,
                                  const QRectF& area ) const
{
    Q_Q(const HbTextItem);

    const int n = mTextLayout.lineCount();
    const qreal leftBorder = q->contentsRect().left()-KFadeTolerance;
    const qreal rightBorder = q->contentsRect().right()+KFadeTolerance;

    QRectF leftRect(area);
    leftRect.setRight(mFadeFromRect.left());
    QRectF centerRect(area);
    centerRect.moveLeft(leftRect.right());
    centerRect.setRight(mFadeFromRect.right());
    QRectF rightRect(area);
    rightRect.setLeft(centerRect.right());

    qreal maxY = area.bottom();

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

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

        QRectF currentCenter(centerRect);

        if(lineRect.top()>maxY) {
            // stop painting line by line
            return i; // current line won't be painted at all
        }

        if(lineRect.left()<leftBorder) {
#ifdef HB_FADE_EFFECT_WORKAROUND_ON_PHONE
            setPainterPen(painter, leftPen, gradientOffset);
#else
            painter->setPen(leftPen);
#endif
            painter->setClipRect(leftRect);
            line.draw(painter, mOffsetPos);
        } else {
            // no fade on this end so extend currentCenter
            currentCenter.setLeft(leftRect.left());
        }

        if(lineRect.right()>rightBorder) {
#ifdef HB_FADE_EFFECT_WORKAROUND_ON_PHONE
            setPainterPen(painter, rightPen, gradientOffset);
#else
            painter->setPen(rightPen);
#endif
            painter->setClipRect(rightRect);
            line.draw(painter, mOffsetPos);
        } else {
            // no fade on this end so extend currentCenter
            currentCenter.setRight(rightRect.right());
        }

        if(currentCenter.width()>0) {
#ifdef HB_FADE_EFFECT_WORKAROUND_ON_PHONE
            setPainterPen(painter, centerPen, gradientOffset);
#else
            painter->setPen(centerPen);
#endif
            painter->setClipRect(currentCenter);
            line.draw(painter, mOffsetPos);
        }

        if(lineRect.bottom()>maxY) {
            // stop painting line by line
            return i; // current line has been painted partially
        }
    } // for loop

    return n;
} // paintFaded()

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

    QLinearGradient gradient;
    setupGradient(&gradient, q->textColor());

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

    const int n = mTextLayout.lineCount();

// #define SEE_FADE_RECTANGLES
#ifdef SEE_FADE_RECTANGLES
    painter->setClipRect(mFadeToRect);
    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 left gradient (//):
        QPointF from(mFadeFromRect.topLeft());
        gradient.setStart(from.x()-mCornerFadeX, from.y()-mCornerFadeY);
        gradient.setFinalStop(from);
        QBrush leftBrush(gradient);
        QPen leftPen;
        leftPen.setBrush(leftBrush);

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

        // 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);

        QRectF clipTo(mFadeToRect);
        clipTo.setBottom(mFadeFromRect.top());
        i = paintFaded(painter, 0, leftPen, centerPen, rightPen, clipTo);
    }

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

    // paint center part
    {
        // left gradient | ||
        gradient.setStart(mFadeToRect.left(), mFadeFromRect.top());
        gradient.setFinalStop(mFadeFromRect.topLeft());
        QBrush leftBrush(gradient);
        QPen leftPen;
        leftPen.setBrush(leftBrush);

        // center with no gradient:
        QPen centerPen(q->textColor());

        // top right gradient || |
        gradient.setStart(mFadeToRect.right(), mFadeFromRect.top());
        gradient.setFinalStop(mFadeFromRect.topRight());
        QBrush rightBrush(gradient);
        QPen rightPen;
        rightPen.setBrush(rightBrush);
        i = paintFaded(painter, i, leftPen, centerPen, rightPen, centerRect);
    }

    // need to draw bottom as faded? is some lines remained?
    if(i<n) {
        // bottom left gradient (\\):
        QPointF from(mFadeFromRect.bottomLeft());
        gradient.setStart(from.x()-mCornerFadeX, from.y()+mCornerFadeY);
        gradient.setFinalStop(from);
        QBrush leftBrush(gradient);
        QPen leftPen;
        leftPen.setBrush(leftBrush);

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

        // 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);

        QRectF clipTo(mFadeToRect);
        clipTo.setTop(mFadeFromRect.bottom());
        i = paintFaded(painter, 0, leftPen, centerPen, rightPen, clipTo);
    }
}

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(!mDontClip) {
        // 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;
}

/*!
    @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->mInvalidateShownText = true;
        prepareGeometryChange();
        d->mText = txt;
        d->mTextLayout.setCacheEnabled(KLayoutCacheLimit >= d->mText.length());
        bool onlyHorizontalSizeHintChanged = false;
        if ( d->mMinLines > 0 && (d->mMinLines == d->mMaxLines) ) {
            onlyHorizontalSizeHintChanged = true;
        }
        if ( (sizePolicy().horizontalPolicy()&QSizePolicy::IgnoreFlag) && onlyHorizontalSizeHintChanged ) {
            // suppress updateGeometry() and use the same geometry
            d->setSize( size() );
        } else {
            updateGeometry();
        }
        update();
    }
}

/*!
    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->mInvalidateShownText = true;
        d->mElideMode = elideMode;
        update();
    }
}

/*!
    \reimp

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

    // 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->mDontPrint) {
        painter->setPen(textColor());

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

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

/*!
    \reimp

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

    HbWidgetBase::setGeometry(rect);

    // needed when there was no size change and some things
    // need to relayout text
    if(d->mInvalidateShownText) {
        prepareGeometryChange();
        d->setSize(rect.size());
    }
}

/*!
    \reimp

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

    return d->boundingRect(contentsRect());
} // boundingRect()

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

    QSizeF size(0,0);

    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 );
    }

    const QFontMetricsF metrics(font());
    QSizeF maxSize(QWIDGETSIZE_MAX, QWIDGETSIZE_MAX);

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

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

                if( d->mMinLines > 1 ) {
                    size.setHeight( ( metrics.height() + metrics.leading() ) * d->mMinLines - metrics.leading() );
                } else {
                    size.setHeight( metrics.height() );
                }
            }

            break;
        }

    case Qt::PreferredSize: 
        {
            if ( !(effectiveOrientations&Qt::Horizontal) && d->mMinLines > 0 && (d->mMinLines == d->mMaxLines) ) {
                //optimize single line if the horizontal sizeHint is ignored
                size.setHeight( ( metrics.height() + metrics.leading() ) * d->mMinLines - metrics.leading() );
                break;
            }

            // do the heavy calculation
            size = metrics.boundingRect(QRectF(QPointF(),maxSize),
                d->textFlagsFromTextOption(),
                d->mText).size();


            if( ( constraint.width() < 0 ) && ( constraint.height() < 0 ) ) {

                if( ( d->mNeedToAdjustSizeHint ) || ( d->oldSize != size ) ) {
                    const_cast<HbTextItemPrivate*>(d)->adjustSizeHint();
                }

                qreal pref =  d->mPrefHeight;

                if( d->mMaxLines > 0 ) {
                    qreal maxLimit =  ( metrics.height() + metrics.leading() ) * d->mMaxLines - metrics.leading();
                    if( maxLimit < pref ) {
                        pref = maxLimit;
                    }

                }

                const_cast<HbTextItemPrivate*>(d)->oldSize = size;
                size.setHeight( pref );
            }

            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->mInvalidateShownText = true;
            updateGeometry();
        }
        break;

    case QEvent::FontChange: {
            Q_D(HbTextItem);
            d->mInvalidateShownText = true;
            prepareGeometryChange();
            updateGeometry();
        }
        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->setSize(event->newSize());

    if( ( qAbs(event->oldSize().width() - event->newSize().width()) > EPSILON ) &&
        ( ( event->oldSize().width() < preferredWidth() ) || ( event->newSize().width() < preferredWidth() ) ) ){
        if( d->adjustSizeHint() ) {
            updateGeometry();
        }
    }
}

/*!
    @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->mInvalidateShownText = true;
            d->mNeedToAdjustSizeHint = true;
            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).

    \sa HbTextItem::isVisible()
 */
void HbTextItem::setTextVisible(bool isVisible)
{
    Q_D(HbTextItem);
    if( d->mDontPrint == isVisible ) {
        d->mDontPrint = !isVisible;
        update();
    }
}

/*!
    Returns if text is visible.

    \sa HbTextItem::setTextVisible(bool)
 */
bool HbTextItem::isTextVisible() const
{
    Q_D(const HbTextItem);
    return !d->mDontPrint;
}

/*!
    enables (default) or disables text clipping when item geometry is too small.

    \sa HbTextItem::isTextClip()
 */
void HbTextItem::setTextClip(bool clipping)
{
    Q_D(HbTextItem);
    if( d->mDontClip == clipping ) {
        prepareGeometryChange();
        d->mDontClip = !clipping;
        setFlag(QGraphicsItem::ItemClipsToShape, clipping);
        update();
    }
}

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

    \sa HbTextItem::setTextClip(bool)
 */
bool HbTextItem::isTextClip() const
{
    Q_D(const HbTextItem);
    return !d->mDontClip;
}

/*!
    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 );
	d->setApiProtectionFlag(HbWidgetBasePrivate::AC_TextLinesMin, true);

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

        d->mMinLines = minLines;
        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);

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

        d->mMaxLines = maxLines;
        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
 */
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
