src/hbwidgets/itemviews/hbgriditemcontainer_p.cpp
author Dremov Kirill (Nokia-D-MSW/Tampere) <kirill.dremov@nokia.com>
Mon, 19 Apr 2010 14:02:13 +0300
changeset 0 16d8024aca5e
child 1 f7ac710697a9
permissions -rw-r--r--
Revision: 201011 Kit: 201015

/****************************************************************************
**
** 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 HbWidgets 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 <hbgriditemcontainer_p.h>
#include <hbgridviewitem.h>
#include <hbgridlayout_p.h>
#include <hbabstractitemview.h>
#include <hbabstractitemview_p.h>
#include <hbgriditemcontainer_p_p.h>
#include <hbmodeliterator.h>

/*!
    \class HbGridItemContainer
    \brief HbGridItemContainer implements HbAbstractItemContainer.
    @internal

    Creates HbGridLayout, manages grid view items.
     
    SeeAlso HbAbstractItemContainer,HbGridView,HbGridViewParameters,HbGridLayout

    IMPORTANT!!
    It is possible that last row will not be full - int this case it contain
    invisible items with have BAD_INDEX. Visibile property of item is also
    used to determine if removed item was really removed or it was previously
    removed from model and currently some item above or bellow should be removed
    (for better understanding look to:
    void HbGridItemContainerPrivate::removeItem(const QModelIndex &index))
*/

/*!
    Constructor.
 */
HbGridItemContainer::HbGridItemContainer(QGraphicsItem *parent)
                    :HbAbstractItemContainer( *new HbGridItemContainerPrivate(), parent)
{
    Q_D(HbGridItemContainer);
    d->q_ptr = this;
    d->init();
}
/*!
    Constructor.
*/
HbGridItemContainer::HbGridItemContainer( HbGridItemContainerPrivate &dd, QGraphicsItem *parent )
                      :HbAbstractItemContainer(dd, parent)
{
    Q_D(HbGridItemContainer);
    d->q_ptr = this;
    d->init();
}

/*!
    Destructor.
 */
HbGridItemContainer::~HbGridItemContainer()
{
}

/*!
    \reimp Adds item for model \a index to container.
*/
void HbGridItemContainer::addItem(const QModelIndex &index, bool animate)
{
    Q_D(HbGridItemContainer);

    if (!index.isValid())
        return;

    int bufferIndex = 0;
    if (d->mItems.count() != 0) {
        bufferIndex = qMax(0, index.row() - d->mItems.first()->modelIndex().row());
    }
    // inserting new item because of buffer size
    if (d->mItems.count() == 0
        || d->mItems.count() < maxItemCount()) {
        insertItem(bufferIndex, index, animate);
        viewLayout()->invalidate();
    }
    // special case - only for grid, if added item is above the 
    // visible region we need to shift all visible items by one!
    else if (d->mItems.count() > 0
        && d->mItems.first()->modelIndex().row() > index.row()) {
        d->shiftUpItem(animate); // shift up in this case always return something
        viewLayout()->invalidate();
    }
    // new item is in visible range
    else if (bufferIndex < d->mItems.count()) {
        HbAbstractViewItem *last = d->mItems.last();
        if (animate) {
            last->setOpacity(0.0);
        }
        setItemModelIndex(last, index);
        viewLayout()->moveItem(last, d->mapToLayoutIndex(bufferIndex), animate);
        d->mItems.move(d->mItems.count() - 1, bufferIndex);
        viewLayout()->invalidate();
    }

    if (d->mItems.count() % d->mItemsPerRow == 1) { // scrollable area size has been changed
        resize(viewLayout()->sizeHint(Qt::PreferredSize));
    }
}

/*!
    \reimp Removes item representing \a index from container.
*/
void HbGridItemContainer::removeItem(const QModelIndex &index, bool animate)
{
    Q_D(HbGridItemContainer);

    d->removeItem(index, animate);
}
    
/*!
    \reimp
*/
void HbGridItemContainer::reset()
{
    Q_D(HbGridItemContainer);
    HbAbstractItemContainer::reset();
    d->resetBuffer();
}

/*!
    \reimp
    Set model indexes starting from \a startIndex. If \a startIndex is not
    a index of first item in a row, then it is calculated automatically 
    (prevent) from different content looking (grid items arrangement should be
    always the same). If there is not enought items function fetch data that
    are before \a startIndex. After calling this function item, with specified 
    index is in container but it position depends on model size.
    If \a startIndex is invlaid then container is filled starting from first 
    item in model.
*/
void HbGridItemContainer::setModelIndexes(const QModelIndex &startIndex)
{
    Q_D(HbGridItemContainer);

    if (!d->mItemView || !d->mItemView->model()) {
        return;
    }

    QModelIndex index = startIndex;
    if (!index.isValid()) {
        index = d->mItemView->model()->index(0, 0);
        if (!index.isValid())
            return;
    }
    index = d->mItemView->model()->index(
                d->alignIndexToClosestFirstInRow(index.row()), 0);

    int modelItemsCount = d->mItemView->model()->rowCount();
    int itemsCount = d->mItems.count();
    int diff = index.row() + itemsCount - modelItemsCount;
    if (diff >= d->mItemsPerRow) {
        diff = modelItemsCount - itemsCount;
        if (diff % d->mItemsPerRow) diff = diff + d->mItemsPerRow - diff % d->mItemsPerRow;
        index = d->mItemView->model()->index(diff, 0);
        if (!index.isValid())
            index = d->mItemView->model()->index(0, 0);
    }

    int i = 0;
    for (; i < itemsCount && index.isValid(); ++i) {
        setItemModelIndex(d->mItems.at(i), index);
        index = d->mItemView->modelIterator()->nextIndex(index);
    }

    if (i < itemsCount) {
        for (; i % d->mItemsPerRow != 0; i++) {
            setItemModelIndex(d->mItems.at(i), index);
        }
        if (i < itemsCount) {
            while (i > d->mItems.count()) {
                d->mItems.removeLast();
            }
        }
    }
}

/*!
    Returns HbGridLayout used by gridview.
*/
HbGridLayout *HbGridItemContainer::viewLayout() const
{
    Q_D(const HbGridItemContainer);
    return d->mLayout;
}

/*!
    \reimp
*/
void HbGridItemContainer::viewResized(const QSizeF &size)
{
    Q_D(HbGridItemContainer);
    if (!(qFuzzyCompare(d->mViewSize.height(), size.height())
            && qFuzzyCompare(d->mViewSize.width(), size.width()))) {
        QPointF p = pos();
        p.setY(p.y() *  size.height() / d->mViewSize.height());
        p.setX(p.x() * size.width() / d->mViewSize.width());
        setPos(p);
        d->mViewSize = size;
        d->resetBuffer();
    }
}

/*!
    \reimp
*/
void HbGridItemContainer::setItemModelIndex(HbAbstractViewItem *item, 
                                            const QModelIndex &index)
{
    Q_D(HbGridItemContainer);

    if (item->modelIndex() != index) {
        d->mLayout->recycleItems(itemByIndex(index), item);
    }

    HbAbstractItemContainer::setItemModelIndex(item, index);
    if (!index.isValid()/* && item->isVisible()*/) {
        // this is needed because this is only way to determine 
        // if there were no item, or it was already removed from model
        item->setVisible(false);
    }
    else if (index.isValid() && !item->isVisible()) {
        item->setVisible(true);
    }
}

/*!
    \reimp
*/

QPointF HbGridItemContainer::recycleItems(const QPointF &delta)
{
    Q_D(HbGridItemContainer);

    if (!d->mItemRecycling || d->mItemsPerRow <=0) {
        return delta;
    }

    QRectF viewRect(d->itemBoundingRect(d->mItemView));
    QSizeF itemsCanvas(layout()->preferredSize());
    qreal invisibleArea = 0;

    qreal diff = 0.0;
    if (Qt::Vertical == d->mScrollDirection) {
        invisibleArea = itemsCanvas.height() - viewRect.height();
        diff = pos().y() - delta.y();
        if ((delta.y() < 0.0 && diff > 0) 
            || (delta.y() > 0.0 && invisibleArea + diff < 0)) {
            diff = delta.y();
        }
        else {
            diff = 0.0;   
        }
    }
    else {
        invisibleArea = itemsCanvas.width() - viewRect.width();
        diff = pos().x() - delta.x();
        if ((delta.x() < 0.0 && diff > 0) 
            || (delta.x() > 0.0 && invisibleArea + diff < 0)) {
            diff = delta.x();
        }
        else {
            diff = 0.0;   
        }
    }

    if (diff != 0.0) {
        if (HbAbstractItemViewPrivate::d_ptr(d->mItemView)->mOptions & HbAbstractItemViewPrivate::PanningActive) {
            // jump is almost in the middle of fetched buffer - in most of cases
            // after scrolling was stopped panning should be done without fetching 
            // items
            // in case when buffer == 1 below lines do not change diff
            qreal extraDiff = invisibleArea/2 - d->mCachedItemHeight;
            if (extraDiff < 0.0) {
                extraDiff = 0.0; // impossible because this mean that bufferSize == 0
            }

            if (diff < 0.0) {
                diff -= extraDiff; 
            }
            else {
                diff += extraDiff;
            }   
        }

        qreal result = (d->recycling(diff));

        QPointF newDelta(Qt::Vertical == d->mScrollDirection
                ?QPointF(0.0, delta.y() - result) 
                : QPointF(delta.x() - result, 0.0));
        
        return newDelta;
    }

    return delta;
}

/*!
    \reimp
*/
void HbGridItemContainer::itemRemoved( HbAbstractViewItem *item, bool animate )
{
    if (item) {
        Q_D(HbGridItemContainer);

        d->mLayout->removeItem(item, animate);
        d->mLayout->invalidate();
    }
}

/*!
    \reimp
*/
void HbGridItemContainer::itemAdded(int index, HbAbstractViewItem *item, bool animate)
{
    if (item) {
        Q_D(HbGridItemContainer);
        if (animate) {
            item->setOpacity(0.0);
        }
        d->mLayout->insertItem(d->mapToLayoutIndex(index), item, animate);
        d->mLayout->invalidate();
     }
}

/*!
    Updates rows and columns count according to changed \a newOrientation.
*/
void HbGridItemContainer::orientationChanged(Qt::Orientation)
{
    Q_D(HbGridItemContainer);
    if(d->mItemView && d->mLayout) {
        qSwap(d->mRowCount, d->mColumnCount);
        d->mLayout->setRowCount(d->mRowCount);
        d->mLayout->setColumnCount(d->mColumnCount);
        d->resetBuffer();
    }
}

/*!
    Items are arranged in gridLayout according to \a scrollDirection
    Qt::Horizontal layout in horizontal direction and Qt::Vertical layout 
    in vertical direction.
*/
void HbGridItemContainer::scrollDirectionChanged(Qt::Orientations scrollDirection)
{
    Q_D(HbGridItemContainer);
    if(d->mScrollDirection != scrollDirection){
        viewLayout()->setScrollDirection(scrollDirection);
        d->mScrollDirection = scrollDirection;
        d->resetBuffer();
    }
}

/*!
   Sets total no of rows to \a rowCount.
*/
void HbGridItemContainer::setRowCount(int rowCount)
{
    Q_D(HbGridItemContainer);

    Q_ASSERT_X(rowCount > 0, "HbGridItemContainer::setRowCount", "row count can not be <= 0");

    if ((rowCount > 0) && (rowCount != d->mRowCount)) {
        d->mRowCount = rowCount;
        viewLayout()->setRowCount(d->mRowCount);
        d->resetBuffer();
    }
}

/*!
   Returns rowCount.
*/
int HbGridItemContainer::rowCount() const
{
    return viewLayout()->rowCount();
}

/*!
    Sets total no of columns to \a columnCount.
*/
void HbGridItemContainer::setColumnCount(int columnCount)
{
    Q_D(HbGridItemContainer);

    Q_ASSERT_X(columnCount > 0, "HbGridItemContainer::setColumnCount", "column count can not be <= 0");

    if ((columnCount > 0) && (columnCount != d->mColumnCount)) {
        d->mColumnCount = columnCount;
        d->mLayout->setColumnCount(d->mColumnCount);
        d->resetBuffer();
    }
}

/*!
    Returns columnCount.
*/
int HbGridItemContainer::columnCount() const
{
    return viewLayout()->columnCount();
}

/*!
    \reimp
*/
int HbGridItemContainer::maxItemCount() const
{
    Q_D(const HbGridItemContainer);

    int count = HbAbstractItemContainer::maxItemCount();
    // inform the grid layout if recycling is on or off
    viewLayout()->setRecycling(d->mItemView->itemRecycling());
    // check if it has been specified, how many items can be show at a time.
    if (d->mItemView->itemRecycling()) {
        count = qMin(d->mMinCount, count);
    }
    return count;
}

/*!
    \reimp
*/
QVariant HbGridItemContainer::itemChange(GraphicsItemChange change, const QVariant & value)
{
    return HbAbstractItemContainer::itemChange(change, value);
}

HbAbstractViewItem *HbGridItemContainer::createDefaultPrototype() const
{
    return new HbGridViewItem();
}

/*!
    Scrolls container to show \a index.
*/
void HbGridItemContainer::scrollTo(const QModelIndex &index, HbAbstractItemView::ScrollHint hint)
{
    Q_D(HbGridItemContainer);

    if (!index.isValid() || d->mItems.count() <= 0)
        return;
    
    switch (hint) {
        case HbAbstractItemView::EnsureVisible: d->scrollToEnsureVisible(index); break;
        case HbAbstractItemView::PositionAtTop: d->scrollToPositionAtTop(index); break;
        case HbAbstractItemView::PositionAtBottom: d->scrollToPositionAtBottom(index); break;
        case HbAbstractItemView::PositionAtCenter: d->scrollToPositionAtCenter(index); break;
    };
}

QModelIndex HbGridItemContainer::lastValidItemIndex() const
{
    Q_D(const HbGridItemContainer);
    return d->lastValidItemIndex();
}

QModelIndex HbGridItemContainer::getViewIndexInTheCenter() const
{
    Q_D(const HbGridItemContainer);
    return HbAbstractItemViewPrivate::d_ptr(d->mItemView)->mVisibleIndex;
}

void HbGridItemContainer::animationFinished(const HbEffect::EffectStatus &status)
{
    Q_D(HbGridItemContainer);

    HbAbstractViewItem *item = static_cast<HbAbstractViewItem *>(status.item);
    
    int itemCount = d->mAnimatedItems.count();
    for (int i = 0; i < itemCount; ++i) {
        QPair<HbAbstractViewItem *, int> animatedItem = d->mAnimatedItems.at(i);
        if (animatedItem.first == item) {
            d->mAnimatedItems.removeAt(i);
            break;
        }
    }

    d->mLayout->removeItem(item, true);  
    item->deleteLater();
}

void HbGridItemContainer::layoutAnimationFinished(QGraphicsLayoutItem *item, HbGridLayout::AnimationType animationType)
{
    Q_D(HbGridItemContainer);

    if (animationType == HbGridLayout::InsertAnimation) {
        item->graphicsItem()->setOpacity(1.0);
        HbEffect::start(item->graphicsItem(), "gridviewitem", "appear", d->mItemView, "_q_animationFinished");
    }
}


#include "moc_hbgriditemcontainer_p.cpp"