src/hbcore/primitives/hbmarqueeitem.cpp
author Dremov Kirill (Nokia-D-MSW/Tampere) <kirill.dremov@nokia.com>
Mon, 04 Oct 2010 00:38:12 +0300
changeset 30 80e4d18b72f5
parent 28 b7da29130b0e
permissions -rw-r--r--
Revision: 201037 Kit: 201039

/****************************************************************************
**
** 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 "hbmarqueeitem.h"
#include "hbmarqueeitem_p.h"
#include "hbwidgetbase_p.h"
#include "hbtextutils_p.h"
#include "hbevent.h"

#ifdef HB_TEXT_MEASUREMENT_UTILITY
#include "hbtextmeasurementutility_r_p.h"
#include "hbfeaturemanager_r.h"
#endif //HB_TEXT_MEASUREMENT_UTILITY

#include "hbdeviceprofile.h"
#include "hbcolorscheme.h"
#include "hbnamespace_p.h"
#include "hbforegroundwatcher_p.h"

#include <qmath.h>
#include <QPainter>
#include <QPropertyAnimation>

//#define HB_DEBUG_MARQUEE_DRAW_RECTS
//#define HB_DEBUG_MARQUEE_LOGS
#ifdef HB_DEBUG_MARQUEE_LOGS
#include <QDebug>
#endif

namespace {
    // The bigger the value the slower the animation
    static const int   ANIMATION_LEAD_TIME = 500;
    static const int   ANIMATION_MAXIMUM_RETURN_TIME = 780;
    static const qreal ANIMATION_IDENT_BY_MM = 8;
    static const qreal ANIMATION_SPEED_METERS_PER_SEC = 0.012; // mm/ms

    static const QString DEFAULT_COLORGROUP = "qtc_view_normal";
}



HbMarqueeContent::HbMarqueeContent(HbMarqueeItem *parent) :
    QGraphicsObject(parent),
    parent(parent),
    mTextDirection(Qt::LeftToRight),
    mTextWidth(0),
    mAlpha(0),
    mFadeLength(0)
{
    setCacheMode(QGraphicsItem::DeviceCoordinateCache);
}


QRectF HbMarqueeContent::boundingRect() const
{
    return mBoundingRect;
}

QPen HbMarqueeContent::pen()
{
    QColor fullColor = parent->textColor();
    QPen pen(fullColor);

    if(parent->contentsRect().width() < mTextWidth) {
        QColor fadeColor = fullColor;
        fadeColor.setAlpha(alpha());

        QLinearGradient gradient;
        gradient.setColorAt(0.0,fullColor);
        gradient.setColorAt(1.0,fadeColor);

        gradient.setStart(gradientStart);
        gradient.setFinalStop(gradientStop);
        pen.setBrush(QBrush(gradient));
    }
    return pen;
}

void HbMarqueeContent::paint(QPainter *painter, const QStyleOptionGraphicsItem *option, QWidget *widget)
{
    Q_UNUSED(option);
    Q_UNUSED(widget);

    painter->save();

    painter->setPen(pen());

    painter->setLayoutDirection ( mTextDirection );
    painter->setFont(parent->font());
    painter->drawText(boundingRect(), Qt::TextDontClip, mText);

#ifdef HB_DEBUG_MARQUEE_DRAW_RECTS
    painter->setPen(Qt::green);
    painter->drawRect(boundingRect());
    painter->setOpacity(0.3);
#endif

    painter->restore();
}


void HbMarqueeContent::updateTextMetaData()
{
    QFontMetricsF metrics(parent->font());

    //calculate bounding rect
    prepareGeometryChange();

    mBoundingRect = metrics.boundingRect(mText);
    mBoundingRect.moveTopLeft(QPointF(0,0));

    // text direction
    bool rightToLeft = HbTextUtils::ImplicitDirectionalityIsRightToLeft(
        mText.utf16(), mText.length(), 0 );
    mTextDirection = rightToLeft ? Qt::RightToLeft : Qt::LeftToRight;

    // Update text width
    mTextWidth = mBoundingRect.width();

    // Update fade length from device profile
    mFadeLength = HbDeviceProfile::profile(this).unitValue()*HbPrivate::TextTruncationFadeWidth;

}


void HbMarqueeContent::setAlpha(int alpha)
{
    mAlpha = alpha;
    update();
}

int HbMarqueeContent::alpha() const
{
    return mAlpha;
}

HbMarqueeItemPrivate::HbMarqueeItemPrivate() :
    content(0),
    mUserRequestedAnimation(false)
    
{
}

void HbMarqueeItemPrivate::init()
{
    Q_Q(HbMarqueeItem);
    content = new HbMarqueeContent(q);

    q->setFlag(QGraphicsItem::ItemClipsChildrenToShape);
    QObject::connect(&mAnimGroup, SIGNAL(stateChanged(QAbstractAnimation::State,QAbstractAnimation::State)) ,q,SLOT(_q_stateChanged()));

#ifdef Q_OS_SYMBIAN
    // this feature has sence only on phone device
    HbForegroundWatcher *fgWatcher = HbForegroundWatcher::instance();
    Q_ASSERT(fgWatcher);
    QObject::connect(fgWatcher, SIGNAL(foregroundLost()), q, SLOT(_q_temporaryStopAnimation()));
    QObject::connect(fgWatcher, SIGNAL(foregroundGained()), q, SLOT(_q_tryToResumeAnimation()));
#endif // Q_OS_SYMBIAN

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


void HbMarqueeItemPrivate::updateTextMetaData()
{
    Q_Q(HbMarqueeItem);

    q->update();
    q->updateGeometry();
    content->updateTextMetaData();
    initContentPosition();
    initAnimations();
    content->update();
}

/*
  Initializes the position of the text so that it can be drawn to the screen
  in the initial position, either elided or not
*/
void HbMarqueeItemPrivate::initContentPosition()
{
    Q_Q(HbMarqueeItem);

    QRectF contentRect = content->boundingRect();
    QRectF rect = q->contentsRect();
    contentRect.moveCenter(rect.center());

    if (q->layoutDirection() == Qt::RightToLeft) {
        contentRect.moveRight(rect.right());
    } else {
        contentRect.moveLeft(rect.left());
    }

    if(rect.width() < content->mTextWidth) {
        if (content->mTextDirection == Qt::RightToLeft) {
            contentRect.moveRight(rect.right());

        } else {
            contentRect.moveLeft(rect.left());
        }
    }
    content->setPos(contentRect.topLeft());

    // Calculate the gradient point only after the content was positioned
    initGradient();

}

void HbMarqueeItemPrivate::initGradient()
{
    Q_Q(HbMarqueeItem);
    QRectF rect = q->contentsRect();

    if(rect.width() < content->mTextWidth) {
        if (content->mTextDirection == Qt::RightToLeft) {
            content->gradientStop = q->mapToItem(content, rect.topRight());
            content->gradientStop.rx() -= rect.width();
            content->gradientStart = content->gradientStop;
            content->gradientStart.rx() += content->mFadeLength;

        } else {
            content->gradientStop = q->mapToItem(content,rect.topLeft());
            content->gradientStop.rx() += rect.width();
            content->gradientStart = content->gradientStop;
            content->gradientStart.rx() -= content->mFadeLength;
        }
        content->setAlpha(0);
    }
}

void HbMarqueeItemPrivate::initAnimations()
{
    Q_Q(HbMarqueeItem);

    bool oldAnimationPending = mUserRequestedAnimation;
    mAnimGroup.clear();
    mUserRequestedAnimation = oldAnimationPending;

    if (q->contentsRect().width() < content->mTextWidth) {

        // get pixel per millimeter value to ensure same animation speed on each device
        qreal ppmValue = HbDeviceProfile::profile(q).ppmValue();

        // Calculate the offset for scrolling
        qreal ident = qMin(ANIMATION_IDENT_BY_MM * ppmValue, q->contentsRect().width());
        qreal scrollOffsetX = content->mTextWidth+ident-q->contentsRect().width();
        qreal v = ANIMATION_SPEED_METERS_PER_SEC*ppmValue; // pisxels per milisecond
        int duration = qRound(scrollOffsetX/v);  // t = s/v in miliseconds

#ifdef HB_DEBUG_MARQUEE_LOGS
        qDebug() << "HbMarqueeItemPrivate::initAnimations "
                << "scrollOffsetX" << scrollOffsetX
                << "HbMarqueeItemPrivate::initAnimations duration" << dura;
#endif // HB_DEBUG_MARQUEE_LOGS

        if (content->mTextDirection != Qt::LeftToRight) {
            scrollOffsetX = -scrollOffsetX;
        }

        QPointF scrolledOutPos(content->pos().x() - scrollOffsetX, content->pos().y());

        mAnimGroup.addPause(ANIMATION_LEAD_TIME);

        QPropertyAnimation *anim = 0;

        anim = new QPropertyAnimation;
        anim->setEasingCurve(QEasingCurve::Linear);
        anim->setTargetObject(content);
        anim->setPropertyName("alpha");
        anim->setStartValue(0);
        anim->setEndValue(0xFF);
        anim->setDuration(1000);
        mAnimGroup.addAnimation(anim);

        anim = new QPropertyAnimation;
        anim->setEasingCurve(QEasingCurve::Linear);
        anim->setTargetObject(content);
        anim->setPropertyName("pos");
        anim->setStartValue(content->pos());
        anim->setEndValue(scrolledOutPos);
        anim->setDuration(duration);
        mAnimGroup.addAnimation(anim);

        anim = new QPropertyAnimation;
        anim->setEasingCurve(QEasingCurve::Linear);
        anim->setTargetObject(content);
        anim->setPropertyName("pos");
        anim->setStartValue(scrolledOutPos);
        anim->setEndValue(content->pos());
        anim->setDuration(qMin(duration, ANIMATION_MAXIMUM_RETURN_TIME));
        mAnimGroup.addAnimation(anim);

        anim = new QPropertyAnimation;
        anim->setEasingCurve(QEasingCurve::Linear);
        anim->setTargetObject(content);
        anim->setPropertyName("alpha");
        anim->setEndValue(0);
        anim->setDuration(1000);
        mAnimGroup.addAnimation(anim);

        if(mAnimGroup.state()!=QAbstractAnimation::Running) {
            _q_tryToResumeAnimation();
        }
    }
}


void HbMarqueeItemPrivate::_q_stateChanged()
{
    Q_Q(HbMarqueeItem);
    if (mAnimGroup.state() == QAbstractAnimation::Running) {
        emit q->animationStarted();
    } else if (mAnimGroup.state() == QAbstractAnimation::Stopped) {
        initContentPosition();
        mUserRequestedAnimation = false;
        emit q->animationStopped();
    } else {
        // Other states are irrelevant
    }
}

/*
    Starts animation if:
    1. user what to animate it
    2. item is visible
    3. item is scene
    4. application is in foreground TODO: missing feature in HbForegroundWatcher how to read current state
 */
void HbMarqueeItemPrivate::_q_tryToResumeAnimation()
{
    Q_Q(HbMarqueeItem);

    if (mUserRequestedAnimation
        && q->isVisible()
        && (q->scene()!=0)) {

#ifdef Q_OS_SYMBIAN
//        if (!HbForegroundWatcher::instance()->) {// missing feature in HbForegroundWatcher
//            return;
//        }
#endif // Q_OS_SYMBIAN

        mAnimGroup.start();
    }
}

void HbMarqueeItemPrivate::_q_temporaryStopAnimation()
{
    // store old state
    bool oldUserRequest = mUserRequestedAnimation;
    q_func()->stopAnimation();

    // restore old state
    mUserRequestedAnimation = oldUserRequest;
}

void HbMarqueeItemPrivate::toggleAnimation(bool startAnimate)
{
    if (startAnimate) {
        if (mAnimGroup.state()!=QAbstractAnimation::Running) {
            _q_tryToResumeAnimation();
        }
    } else {
        _q_temporaryStopAnimation();
    }
}

/*!
  @alpha
  @hbcore
 \class HbMarqueeItem
 \brief HbMarqueeItem is a lightweight item for showing a single line marqueed text.


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

 */

/*!
 Constructor for the class.
 */

HbMarqueeItem::HbMarqueeItem(QGraphicsItem *parent) :
    HbWidgetBase(*new HbMarqueeItemPrivate, parent)
{
    Q_D(HbMarqueeItem);
    d->init();
}

/*!
 Text can be set with \a text attribute.
 */
HbMarqueeItem::HbMarqueeItem(const QString &text, QGraphicsItem *parent) :
    HbWidgetBase(*new HbMarqueeItemPrivate, parent)
{
    Q_D(HbMarqueeItem);
    d->init();
    setText(text);
}

HbMarqueeItem::HbMarqueeItem(HbMarqueeItemPrivate &dd, QGraphicsItem *parent) :
    HbWidgetBase(dd, parent)
{
    Q_D(HbMarqueeItem);
    d->init();
}

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

}

/*!
    Returns the text

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


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

    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, 1);
            txt = text.left(index);
        } else {
            setProperty(HbTextMeasurementUtilityNameSpace::textIdPropertyName,  QVariant::Invalid);
        }
    }
#endif // HB_TEXT_MEASUREMENT_UTILITY

    if (d->content->mText != txt) {
        d->content->mText = txt;
        d->updateTextMetaData();
        d->content->update();
    }
}

/*!
    Returns if the text is currently animating.

    Note that if marquee item is not visible (isVisible is false or
    it is not in scene or phone in in sleep mode) then animation is stoped and
    this method will return false even when startAnimation() was called.
 */
bool HbMarqueeItem::isAnimating() const
{
    Q_D(const HbMarqueeItem);
    return (d->mAnimGroup.state()==QAbstractAnimation::Running);
}

/*!
    Starts or restarts the animation of the text when marquee item
    became/is visible and is inside of scene.
 */
void HbMarqueeItem::startAnimation()
{
    Q_D(HbMarqueeItem);

    d->mUserRequestedAnimation = true;
    d->_q_tryToResumeAnimation();
}

/*!
    Stops the animation of the text if it is ongoing.
 */
void HbMarqueeItem::stopAnimation()
{
    Q_D(HbMarqueeItem);
    d->mAnimGroup.stop();
    d->mUserRequestedAnimation = false;
}

/*!
    Returns the loop count of the animation.

    \sa setLoopCount
 */
int HbMarqueeItem::loopCount() const
{
    Q_D(const HbMarqueeItem);
    return d->mAnimGroup.loopCount();
}

/*!
    Sets the loop count of the animation.
    The loop count determines how many times the animation is repeated
    after the animation started.
    Default value: 1
    Value -1 makes the animation continues and only stops when stopAnimation is called

    \sa loopCount
 */
void HbMarqueeItem::setLoopCount(int count)
{
    Q_D(HbMarqueeItem);
    d->mAnimGroup.setLoopCount(count);
}

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

    QSizeF size;

    switch(which) {
    case Qt::MinimumSize: {
            if (d->content->mText.isEmpty()) {
                return QSizeF(0.f, 0.f);
            }

            size = d->content->boundingRect().size();
            size.setWidth(qMin( size.width() , size.height()));
            size.setHeight(size.height());

            break;
        }

    case Qt::PreferredSize: {
            size = d->content->boundingRect().size();
            break;
        }

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

    return size;
}


 /*!
    \reimp
 */
void HbMarqueeItem::changeEvent(QEvent *event)
{
    Q_D(HbMarqueeItem);

    if(event->type() == HbEvent::FontChange ||
       event->type() == HbEvent::LayoutDirectionChange) {
        d->updateTextMetaData();
    }
    if (event->type() == HbEvent::ThemeChanged) {
        d->mDefaultColor = QColor();
        if(!d->mColor.isValid()) {
           update();
           d->content->update();
        }
    }
    HbWidgetBase::changeEvent(event);
}

/*!
    \reimp
 */
void HbMarqueeItem::resizeEvent(QGraphicsSceneResizeEvent *event)
{
    Q_D(HbMarqueeItem);
    HbWidgetBase::resizeEvent(event);
    d->updateTextMetaData();
}


/*!
    \reimp
 */
QVariant HbMarqueeItem::itemChange(GraphicsItemChange change, const QVariant &value)
{
    Q_D(HbMarqueeItem);

    switch (change) {
    case QGraphicsItem::ItemVisibleHasChanged: {
            d->toggleAnimation(value.toBool());
        }
        break;

    case QGraphicsItem::ItemSceneHasChanged: {
            d->toggleAnimation(0!=scene());
        }
        break;

    default:
        ;// nothing to do
    }
    return HbWidgetBase::itemChange(change, value);
}

/*!
    \reimp
*/
bool HbMarqueeItem::event(QEvent *e)
{
    Q_D(HbMarqueeItem);
    if (e->type() == HbEvent::SleepModeEnter) {
        d->_q_temporaryStopAnimation();
    } else if (e->type() == HbEvent::SleepModeExit) {
        d->_q_tryToResumeAnimation();
    }
    return HbWidgetBase::event(e);
}



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

    \sa HbTextItem::textColor()
 */
void HbMarqueeItem::setTextColor(const QColor &color)
{
    Q_D(HbMarqueeItem);
    d->setApiProtectionFlag(HbWidgetBasePrivate::AC_TextColor, true);
    if (d->mColor != color) {
        d->mColor = color;
        update();
        d->content->update();
    }
}

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

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

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

#include "moc_hbmarqueeitem.cpp"
// end of file