ganeswidgets/src/hgcoverflowcontainer.cpp
author Dremov Kirill (Nokia-D-MSW/Tampere) <kirill.dremov@nokia.com>
Mon, 03 May 2010 13:32:54 +0300
changeset 1 e48454f237ca
parent 0 89c329efa980
child 2 49c70dcc3f17
permissions -rw-r--r--
Revision: 201015 Kit: 201018

/*
* Copyright (c) 2010 Nokia Corporation and/or its subsidiary(-ies).
* All rights reserved.
* This component and the accompanying materials are made available
* under the terms of "Eclipse Public License v1.0"
* which accompanies this distribution, and is available
* at the URL "http://www.eclipse.org/legal/epl-v10.html".
*
* Initial Contributors:
* Nokia Corporation - initial contribution.
*
* Contributors:
*
* Description:
*
*/

#include <QGesture>
#include <QGraphicsSceneResizeEvent>
#include <QPainter>
#include <hblabel.h>
#include "hgcoverflowcontainer.h"
#include "hgmediawallrenderer.h"
#include "hgwidgetitem.h"
#include "trace.h"


static const qreal KCameraMaxYAngle(20);
static const qreal KSpringVelocityToCameraYAngleFactor(2);
static const int   KLabelMargin(4);

HgCoverflowContainer::HgCoverflowContainer(
    QGraphicsItem* parent) : HgContainer(parent),
    mTitleLabel(0),
    mDescriptionLabel(0),
    mTitlePosition(HgMediawall::PositionAboveImage),
    mDescriptionPosition(HgMediawall::PositionNone),
    mPrevPos(-1),
    mAspectRatio(1),
    mDrawableRect(rect())
{
    mTitleLabel = new HbLabel(this);
    mTitleLabel->setZValue(zValue()+1);
    mTitleLabel->setAlignment(Qt::AlignCenter);
    mTitleLabel->setVisible(false);

    mDescriptionLabel = new HbLabel(this);
    mDescriptionLabel->setZValue(zValue()+1);
    mDescriptionLabel->setAlignment(Qt::AlignCenter);
    mDescriptionLabel->setVisible(false);
    
    mUserItemSize = QSize(250,250);
    mUserItemSpacing = QSize(1,1);
}

HgCoverflowContainer::~HgCoverflowContainer()
{
}

// events
void HgCoverflowContainer::paint(QPainter *painter, const QStyleOptionGraphicsItem *option, QWidget *widget)
{
    HgContainer::paint(painter, option, widget);
}

void HgCoverflowContainer::resizeEvent(QGraphicsSceneResizeEvent *event)
{
    FUNC_LOG;

    HbWidget::resizeEvent(event);

    updatePositions();    
}

// from HgContainer
HgMediaWallRenderer* HgCoverflowContainer::createRenderer(Qt::Orientation scrollDirection)
{
    HgMediaWallRenderer* renderer = new HgMediaWallRenderer(this, scrollDirection, true);
    renderer->setImageSize(mUserItemSize);
    renderer->enableCoverflowMode(true);
    renderer->setRowCount(1, renderer->getImageSize(), false);
    renderer->enableReflections(true);
    renderer->setSpacing(mUserItemSpacing);
    renderer->setFrontCoverElevationFactor(0.5);
    return renderer;
}

qreal HgCoverflowContainer::getCameraDistance(qreal springVelocity)
{
    return qAbs(springVelocity * 0.01f);
}

qreal HgCoverflowContainer::getCameraRotationY(qreal springVelocity)
{
    return qBound(-KCameraMaxYAngle, springVelocity * KSpringVelocityToCameraYAngleFactor, KCameraMaxYAngle);
}

void HgCoverflowContainer::handleTapAction(const QPointF& pos, HgWidgetItem* hitItem, int hitItemIndex)
{
    Q_UNUSED(pos)

    if (qAbs(qreal(hitItemIndex) - mSpring.pos().x()) < 0.01f)
    {
        emit activated(hitItem->modelIndex());
    }
    else
    {
        mSpring.animateToPos(QPointF(hitItemIndex, 0));
    }
}

void HgCoverflowContainer::handleLongTapAction(const QPointF& pos, HgWidgetItem* hitItem, int hitItemIndex)
{
    Q_UNUSED(hitItemIndex)

    emit longPressed(hitItem->modelIndex(), pos);
}

void HgCoverflowContainer::onScrollPositionChanged(qreal pos)
{
    HgContainer::onScrollPositionChanged(pos);

    qreal ipos = floorf(pos);
    qreal frac = pos - ipos;
    qreal p = frac > 0.5 ? ipos + 1.0f : ipos;

    if (mPrevPos != (int)p) {
        mPrevPos = (int)p;        
        HgWidgetItem* item = itemByIndex((int)p);
        if (item && item->modelIndex() != mSelectionModel->currentIndex()) {
            mSelectionModel->setCurrentIndex(item->modelIndex(), QItemSelectionModel::Current);
        }
    }
}

void HgCoverflowContainer::handleCurrentChanged(const QModelIndex &current)
{
    FUNC_LOG;

    if (current.isValid()) {
        updateLabels(current.row());
    }
}

void HgCoverflowContainer::itemDataChanged(const int &firstIndex, const int &lastIndex)
{
    FUNC_LOG;
    HANDLE_ERROR_NULL(mSelectionModel); // If model has been set, also is selection model

    HgContainer::itemDataChanged(firstIndex, lastIndex);

    if (mSelectionModel->currentIndex().isValid()) {
        int current = mSelectionModel->currentIndex().row();
        if (firstIndex <= current && current <= lastIndex) {
            updateLabels(current);
        }
    }

    if (firstIndex == 0) {
        // Take preferred aspect ratio from the first image
        const HgImage *firstImage = image(0);
        if (firstImage && firstImage->height() != 0) {
            mAspectRatio = qMax((qreal)0.1, (qreal)firstImage->width()/firstImage->height()); // Don't let aspect ratio go to 0
            updatePositions();
        }
    }
}

void HgCoverflowContainer::setTitlePosition(HgMediawall::LabelPosition position)
{
    FUNC_LOG;

    if (mTitlePosition != position) {
        mTitlePosition = position;
        updatePositions();
    }
}

HgMediawall::LabelPosition HgCoverflowContainer::titlePosition() const
{
    FUNC_LOG;

    return mTitlePosition;
}

void HgCoverflowContainer::setDescriptionPosition(HgMediawall::LabelPosition position)
{
    FUNC_LOG;

    if (mDescriptionPosition != position) {
        mDescriptionPosition = position;
        updatePositions();
    }
}

HgMediawall::LabelPosition HgCoverflowContainer::descriptionPosition() const
{
    FUNC_LOG;

    return mDescriptionPosition;
}

void HgCoverflowContainer::setTitleFontSpec(const HbFontSpec &fontSpec)
{
    FUNC_LOG;

    if (!mTitleLabel) return;
    if (mTitleLabel->fontSpec() != fontSpec) {
        mTitleLabel->setFontSpec(fontSpec);
        updatePositions();
    }
}

HbFontSpec HgCoverflowContainer::titleFontSpec() const
{
    FUNC_LOG;

    if (!mTitleLabel) return HbFontSpec();
    return mTitleLabel->fontSpec();
}

void HgCoverflowContainer::setDescriptionFontSpec(const HbFontSpec &fontSpec)
{
    FUNC_LOG;

    if (!mDescriptionLabel) return;
    if (mDescriptionLabel->fontSpec() != fontSpec) {
        mDescriptionLabel->setFontSpec(fontSpec);
        updatePositions();
    }
}

HbFontSpec HgCoverflowContainer::descriptionFontSpec() const
{
    FUNC_LOG;

    if (!mDescriptionLabel) return HbFontSpec();
    return mDescriptionLabel->fontSpec();
}

void HgCoverflowContainer::calculatePositions()
{
    FUNC_LOG;
    HANDLE_ERROR_NULL(mTitleLabel);
    HANDLE_ERROR_NULL(mDescriptionLabel);
    
    int height = size().height();
    int width = size().width();
    int titleHeight = QFontMetrics(mTitleLabel->effectiveFontSpec().font()).height();
    int descriptionHeight = QFontMetrics(mDescriptionLabel->effectiveFontSpec().font()).height();
    qreal usableHeight = height-KLabelMargin;
    if (mTitlePosition != HgMediawall::PositionNone) {
        usableHeight -= (titleHeight+KLabelMargin);
    }
    if (mDescriptionPosition != HgMediawall::PositionNone) {
        usableHeight -= (descriptionHeight+KLabelMargin);
    }

    usableHeight *= 0.8; // Leave some space for the reflection
    if (usableHeight <= 0) return;

    qreal usableWidth = width/2;
    if (usableWidth <= 0) return;

    mDrawableRect = rect();
    QSizeF imageSize;
    if (usableWidth/usableHeight > mAspectRatio) {
        imageSize.setHeight(usableHeight);
        imageSize.setWidth(mAspectRatio*usableHeight);
    }
    else {
        imageSize.setWidth(usableWidth);
        imageSize.setHeight(usableWidth/mAspectRatio);
        mDrawableRect.setTop((usableHeight-imageSize.height())/2);
    }

    QRectF titleGeometry(0, mDrawableRect.top()+KLabelMargin, width, titleHeight);
    QRectF descriptionGeometry(0, mDrawableRect.top()+KLabelMargin, width, descriptionHeight);

    if (mTitlePosition == HgMediawall::PositionAboveImage &&
        mDescriptionPosition == HgMediawall::PositionAboveImage) {
        // titleGeometry default is ok
        descriptionGeometry.moveTop(titleGeometry.bottom()+KLabelMargin);
        mDrawableRect.setTop(descriptionGeometry.bottom()+KLabelMargin);
    }
    else if (mTitlePosition == HgMediawall::PositionBelowImage &&
             mDescriptionPosition == HgMediawall::PositionBelowImage) {
        titleGeometry.moveTop(mDrawableRect.top()+imageSize.height()+KLabelMargin);
        descriptionGeometry.moveTop(titleGeometry.bottom()+KLabelMargin);
    }
    else {
        if (mTitlePosition == HgMediawall::PositionAboveImage) {
            // titleGeometry default is ok
            mDrawableRect.setTop(titleGeometry.bottom()+KLabelMargin);
        }
        else if (mDescriptionPosition == HgMediawall::PositionAboveImage) {
            // descriptionGeometry default is ok
            mDrawableRect.setTop(descriptionGeometry.bottom()+KLabelMargin);
        }

        if (mTitlePosition == HgMediawall::PositionBelowImage) {
            titleGeometry.moveTop(mDrawableRect.top()+imageSize.height()+KLabelMargin);
        }
        else if (mDescriptionPosition == HgMediawall::PositionBelowImage) {
            descriptionGeometry.moveTop(mDrawableRect.top()+imageSize.height()+KLabelMargin);
        }
    }

    INFO("Setting image size to:" << imageSize << "(total size:" << QSize(width, height)
        << "usable size:" << QSizeF(usableWidth, usableHeight) << ", aspect ratio is:" << mAspectRatio << ")" << "Drawable rect:" << mDrawableRect);
    mRenderer->setImageSize(imageSize);
    mAutoSize = imageSize;
    mDrawableRect.setHeight(imageSize.height()/0.8);

    if (mTitlePosition != HgMediawall::PositionNone) {
        INFO("Title geometry:" << titleGeometry);
        mTitleLabel->setGeometry(titleGeometry);
        mTitleLabel->setVisible(true);
    }
    else {
        mTitleLabel->setVisible(false);
    }
    if (mDescriptionPosition != HgMediawall::PositionNone) {
        INFO("Description geometry:" << descriptionGeometry);
        mDescriptionLabel->setGeometry(descriptionGeometry);
        mDescriptionLabel->setVisible(true);
    }
    else {
        mDescriptionLabel->setVisible(false);
    }

    // This may be called before selection model is set.
    if (mSelectionModel && mSelectionModel->currentIndex().isValid()) {
        updateLabels(mSelectionModel->currentIndex().row());
    }
    
    mRenderer->setSpacing(QSizeF(1,1));
        
}

void HgCoverflowContainer::positionLabels()
{
    FUNC_LOG;
    HANDLE_ERROR_NULL(mTitleLabel);
    HANDLE_ERROR_NULL(mDescriptionLabel);
    HANDLE_ERROR_NULL(mSelectionModel);

    int centerIconTop = (size().height() - mRenderer->getImageSize().height()) / 2;

    int height = size().height();
    int width = size().width();
    int titleHeight = QFontMetrics(mTitleLabel->effectiveFontSpec().font()).height();
    int descriptionHeight = QFontMetrics(mDescriptionLabel->effectiveFontSpec().font()).height();

    if (mTitlePosition == HgMediawall::PositionAboveImage &&
        mDescriptionPosition == HgMediawall::PositionAboveImage) {
        mTitleLabel->setGeometry(QRectF(
            0,
            qMax(KLabelMargin, centerIconTop-2*KLabelMargin-titleHeight-descriptionHeight),
            width, titleHeight));
        mDescriptionLabel->setGeometry(QRectF(
            0,
            mTitleLabel->geometry().bottom()+KLabelMargin,
            width, descriptionHeight));
    }
    else if (mTitlePosition == HgMediawall::PositionBelowImage &&
             mDescriptionPosition == HgMediawall::PositionBelowImage) {
        mDescriptionLabel->setGeometry(QRectF(
            0,
            height-descriptionHeight-KLabelMargin,
            width, descriptionHeight));
        mTitleLabel->setGeometry(QRectF(
            0,
            mDescriptionLabel->geometry().top()-titleHeight-KLabelMargin,
            width, titleHeight));
    }
    else {
        if (mTitlePosition == HgMediawall::PositionAboveImage) {
            mTitleLabel->setGeometry(QRectF(
                0,
                qMax(KLabelMargin, centerIconTop-KLabelMargin-titleHeight),
                width, titleHeight));
        }
        else if (mTitlePosition == HgMediawall::PositionBelowImage) {
            mTitleLabel->setGeometry(QRectF(
                0,
                height-titleHeight-KLabelMargin,
                width, titleHeight));
        }

        if (mDescriptionPosition == HgMediawall::PositionAboveImage) {
            mDescriptionLabel->setGeometry(QRectF(
                0,
                qMax(KLabelMargin, centerIconTop-KLabelMargin-descriptionHeight),
                width, descriptionHeight));
        }
        else if (mDescriptionPosition == HgMediawall::PositionBelowImage) {
            mDescriptionLabel->setGeometry(QRectF(
                0,
                height-descriptionHeight-KLabelMargin,
                width, descriptionHeight));
        }
    }

    mTitleLabel->setVisible(mTitlePosition != HgMediawall::PositionNone);
    mDescriptionLabel->setVisible(mDescriptionPosition != HgMediawall::PositionNone);

    INFO("Title geometry:" << mTitleLabel->geometry() << "visible:" << mTitleLabel->isVisible());
    INFO("Description geometry:" << mDescriptionLabel->geometry() << "visible:" << mDescriptionLabel->isVisible());

    if ( mSelectionModel &&  mSelectionModel->currentIndex().isValid()) {
        updateLabels(mSelectionModel->currentIndex().row());
    }
}


void HgCoverflowContainer::updateLabels(int itemIndex)
{
    FUNC_LOG;
    HANDLE_ERROR_NULL(mTitleLabel);
    HANDLE_ERROR_NULL(mDescriptionLabel);

    if (itemIndex >= 0 && itemIndex < mItems.count()) {
        mTitleLabel->setPlainText(mItems.at(itemIndex)->title());
        mDescriptionLabel->setPlainText(mItems.at(itemIndex)->description());
    }
}

void HgCoverflowContainer::scrollToPosition(const QPointF& pos, bool animate)
{
    QPointF p = pos;
    p.setX((int)pos.x());
    HgContainer::scrollToPosition(p,animate);
}

QRectF HgCoverflowContainer::drawableRect() const
{
    if (mItemSizePolicy == HgWidget::ItemSizeAutomatic)
        return mDrawableRect;
    
    return rect();
}

void HgCoverflowContainer::setDefaultImage(QImage defaultImage)
{
    HgContainer::setDefaultImage(defaultImage);

    if (!defaultImage.isNull()) {
        mAspectRatio = qMax((qreal)0.1, (qreal)defaultImage.width()/defaultImage.height()); // Don't let aspect ratio go to 0
        updatePositions();
    }
}

QSizeF HgCoverflowContainer::getAutoItemSize() const
{
    return mAutoSize;
}

QSizeF HgCoverflowContainer::getAutoItemSpacing() const
{    
    return QSizeF(1,1);
}

void HgCoverflowContainer::updateItemSizeAndSpacing()
{
    HgContainer::updateItemSizeAndSpacing();
    
    updatePositions();
}


void HgCoverflowContainer::updatePositions()
{
    if (mItemSizePolicy == HgWidget::ItemSizeAutomatic)
        calculatePositions();
    else
    {
        positionLabels();
    }
}

void HgCoverflowContainer::setFrontItemPositionDelta(const QPointF& position)
{
    if (!mRenderer)
        return;
    
    mRenderer->setFrontItemPosition(position);
}

QPointF HgCoverflowContainer::frontItemPositionDelta() const
{
    return mRenderer ? mRenderer->frontItemPosition() : QPointF();
}

void HgCoverflowContainer::enableReflections(bool enabled)
{
    if (mRenderer)
        mRenderer->enableReflections(enabled);
}

bool HgCoverflowContainer::reflectionsEnabled() const
{
    return mRenderer ? mRenderer->reflectionsEnabled() : false;
}