/****************************************************************************
**
** 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 "hbabstractitemcontainer_p.h"
#include "hbabstractitemcontainer_p_p.h"

#include "hbabstractviewitem.h"
#include "hbabstractitemview.h"
#include "hbmodeliterator.h"
#include <hbapplication.h>

#include <QGraphicsLayout>
#include <QGraphicsSceneResizeEvent>
#include <QEvent>
#include <QDebug>


/*!   
    HbAbstractItemContainer is used in HbAbstractItemView to hold the layout and view items.
    Container should have a layout, otherwise its size is zero always.
    HbAbstractItemContainer can have any kind of layout as it child.
*/

/*!
    Function is called after new \a item was added into \a index position into
    container.
*/

/*!
    Function is called after the \a item was removed from the container. 
*/

/*!
    Function is called when container needs to be resized.
*/

/*!
    Returns the default prototype.

    Subclasses of this class must implement this function to introduce their own default prototype. 
    Default prototype is used to create view items unless the class user excplicitly sets prototype with setItemPrototype method. 
    Implementation of this method must construct and return a new view item widget. 
*/

const int HB_DEFAULT_BUFFERSIZE = 4;
const int UpdateItemBufferEvent = QEvent::registerEventType();

HbAbstractItemContainerPrivate::HbAbstractItemContainerPrivate() : 
    HbWidgetPrivate(),
    mItemView(0),
    mBufferSize(HB_DEFAULT_BUFFERSIZE),
    mItemRecycling(false),
    mUniformItemSizes(false)
{
}

HbAbstractItemContainerPrivate::~HbAbstractItemContainerPrivate()
{
}


void HbAbstractItemContainerPrivate::init()
{
    Q_Q(HbAbstractItemContainer);
    q->setSizePolicy(QSizePolicy::Ignored, QSizePolicy::Ignored);

    q->setFlag(QGraphicsItem::ItemHasNoContents, true);
}

/*!
   Returns given item's bounding rectangle in scene coordinates.
 */
QRectF HbAbstractItemContainerPrivate::itemBoundingRect(const QGraphicsItem *item) const
{
    Q_Q(const HbAbstractItemContainer);
    if (q->layout()) {
        q->layout()->activate();
    }
    return item->mapToItem(mItemView, item->boundingRect()).boundingRect();
}

void HbAbstractItemContainerPrivate::firstAndLastVisibleBufferIndex(
        int& firstVisibleBufferIndex,
        int& lastVisibleBufferIndex,
        const QRectF &viewRect,
        bool fullyVisible) const
{
    Q_Q(const HbAbstractItemContainer);

    if (q->layout() && !q->layout()->isActivated()) {
        q->layout()->activate();
    }

    firstVisibleBufferIndex = -1;
    lastVisibleBufferIndex = -1;

    int count = mItems.count();
    for (int i = 0; i < count; ++i) {
        if (visible(mItems.at(i), viewRect, fullyVisible)) {
            if (firstVisibleBufferIndex == -1) {
                firstVisibleBufferIndex = i;
            }
            lastVisibleBufferIndex = i;
        } else if ( lastVisibleBufferIndex != -1 ) {
            // no need to check the remaining ones.
            break;
        }
    }
}

/*!
    \private

    Returns true if given item is located within viewport (i.e.  view), otherwise
    returns false. If fullyVisible parameter is true method will return true only
    for item that is shown fully. In this case for partially visible items false is returned.
*/
bool HbAbstractItemContainerPrivate::visible(HbAbstractViewItem* item, const QRectF &viewRect, bool fullyVisible) const
{
    if (item) {
        QRectF itemRect(itemBoundingRect(item));
        // 0.5 tolerance otherwise it fail - for instance if pos.y = -5.68434e-14 then item will be not fullyVisible
        itemRect.adjust(0.5, 0.5, -0.5, -0.5);
        if (fullyVisible) {
            if (viewRect.contains(itemRect)) {
                return true;
            }
        } else {
            if (viewRect.intersects(itemRect)) {
                return true;
            }
        }
    }

    return false;
}

/*!
  Clears the prototype list and deletes the prototypes.
*/
void HbAbstractItemContainerPrivate::deletePrototypes()
{
    qDeleteAll(mPrototypes);
    mPrototypes.clear();
}

int HbAbstractItemContainerPrivate::findStateItem(const QModelIndex &index) const
{
    for (int current = 0; current < mItemStateList.count(); ++current) {
        if (mItemStateList.at(current).index == index) {
            return current;
        }
    }
    return -1;
}

void HbAbstractItemContainerPrivate::initPrototype(HbAbstractViewItem *prototype) const
{
    prototype->setParentItem(mItemView);
    prototype->setItemView(mItemView);
    prototype->resize(QSizeF(0, 0));
    prototype->hide();
}

HbAbstractViewItem* HbAbstractItemContainerPrivate::createItem(const QModelIndex& index)
{
    Q_Q(HbAbstractItemContainer);

    HbAbstractViewItem *result = 0;
    HbAbstractViewItem *prototype = itemPrototype(index);
    if (prototype) {
        result = q_check_ptr(prototype->createItem());
        Q_ASSERT_X(result->prototype() == prototype, "HbAbstractItemContainerPrivate::createItem", "Copy constructor must be used for creating concrete view items in createItem(). Create your custom view item with 'new YourCustomViewItem(*this)' instead of 'new YourCustomViewItem(this)'");
        result->setParentItem(q);

        emit q->itemCreated(result);
    }
    return result;
}

HbAbstractViewItem* HbAbstractItemContainerPrivate::itemPrototype(const QModelIndex& index) const
{
    Q_Q(const HbAbstractItemContainer);

    if (mPrototypes.isEmpty()) {
        HbAbstractViewItem *defaultPrototype = q->createDefaultPrototype();
        if (defaultPrototype) {
            initPrototype(defaultPrototype);

            mPrototypes.append(defaultPrototype);
        }
    }

    HbAbstractViewItem *result = 0;
    int count = mPrototypes.count() - 1;
    for (int i = count; i >= 0; i--) {
        if (mPrototypes[i]->canSetModelIndex(index)) {
            result = mPrototypes[i];
            break;
        }
    }
    return result;
}


/*!
    \private
    
    Updates item buffer to contain correct amount of items for the current situation.
*/
void HbAbstractItemContainerPrivate::updateItemBuffer()
{
    Q_Q(HbAbstractItemContainer);

    if (!mItemView) {
        return;
    }

    int targetCount = q->maxItemCount();
    int itemCount = mItems.count();

    if (itemCount == targetCount) {
        return;
    }
    
    // Store the first item position related to view.
    QPointer<HbAbstractViewItem> firstItem = mItems.value(qMax(0, itemCount - targetCount));
    QPointF firstItemPos;
    if (firstItem) {
        firstItemPos = itemBoundingRect(firstItem).topLeft();
    }

    // Perform the incresing/decreasing
    if (itemCount < targetCount) {
        increaseBufferSize(targetCount - itemCount);
    } else {
        decreaseBufferSize(itemCount - targetCount);
    }

    restoreItemPosition(firstItem, firstItemPos);
}

/*!
    Increases the item buffer size with given \a amount.

    First tries to add as many items after the last item with valid
    index as it can. If there is not enough valid indexes then inserts items
    before the first item with valid index.
*/
void HbAbstractItemContainerPrivate::increaseBufferSize(int amount)
{
    Q_Q(HbAbstractItemContainer);

    // Append new items.
    QModelIndex index;
    for (int i = mItems.count() - 1; i >= 0; --i) {
        index = mItems.at(i)->modelIndex();
        if (index.isValid()) {
            break;
        }
    }

    int itemsAdded = 0;
    // in practize following conditions must apply: itemview is empty and scrollTo() has been called.
    // Starts populating items from given mFirstItemIndex
    if (    !index.isValid()
        &&  mFirstItemIndex.isValid()) {
        index = mFirstItemIndex;
        q->insertItem(mItems.count(), index);
        itemsAdded++;

        mFirstItemIndex = QModelIndex();
    }

    while (itemsAdded < amount) {
        index = mItemView->modelIterator()->nextIndex(index);
        if (!index.isValid()) {
            break;
        }
        
        q->insertItem(mItems.count(), index);
        itemsAdded++;
    }

    if (itemsAdded == amount) {
        return;
    } 

    // Prepend new items.
    for (int i = 0; i < mItems.count(); ++i) {
        index = mItems.at(i)->modelIndex();
        if (index.isValid()) {
            break;
        }
    }

    while (itemsAdded < amount) {
        index = mItemView->modelIterator()->previousIndex(index);
        if (!index.isValid()) {
            break;
        }

        q->insertItem(0, index);
        itemsAdded++;
    }
}

/*!
    Decreases the item buffer size with given \a amount.

    Tries to avoid deleting visible items and keep the buffer balanced.
*/
void HbAbstractItemContainerPrivate::decreaseBufferSize(int amount)
{
    Q_UNUSED(amount)

    int firstVisible = 0;
    int lastVisible = 0;
    firstAndLastVisibleBufferIndex(firstVisible, lastVisible, mItemView->geometry(), false);

    int deletableItemsOnTop = firstVisible - 1;
    int deletableItemsOnBottom = mItems.count() - lastVisible - 1;
    int itemsDeleted = 0;
    HbAbstractViewItem* item = 0;

    // in decreasing the buffer we try to keep it balanced
    while (itemsDeleted < amount ) {
        if (deletableItemsOnTop > deletableItemsOnBottom) {
            item = mItems.takeFirst();
            deletableItemsOnTop--;
        } else {
            item = mItems.takeLast();
            deletableItemsOnBottom--;
        }
        deleteItem(item);
        ++itemsDeleted;
     }

}

/*!
    \private
*/
HbAbstractViewItem* HbAbstractItemContainerPrivate::item(const QModelIndex &index) const
{
    int itemCount = mItems.count();
    if (index.isValid()) {
        if (itemCount > 0) { 
            HbModelIterator *iterator = mItemView->modelIterator();
            int positionFirstIndex = iterator->indexPosition(mItems.first()->modelIndex());

            // when new items are inserted, they will appear in the end of container before a model index is assigned to them
            if (positionFirstIndex >= 0) {
                int positionIndex = iterator->indexPosition(index);
                HbAbstractViewItem *item = mItems.value(positionIndex - positionFirstIndex);
                if (    item
                    &&  item->modelIndex() == index) { 
                    return item;
                }
            }
        }
    } else {
        // searching items e.g. removed from model 
        for (int i = 0; i < itemCount; ++i) {
            if (!mItems.at(i)->modelIndex().isValid()) {
                return mItems.at(i);
            }
        }
    }
    return 0;
}

/*!
    \private

    Removes \a item with model index \a index.
*/
void HbAbstractItemContainerPrivate::doRemoveItem(HbAbstractViewItem *item, const QModelIndex &index, bool animate)
{
    if (item) {
        deleteItem(item, animate);
        if (!index.isValid()) {
            mItemStates.remove(index);
        }
    }
}


/*!
    \private

    Deletes \a item.
*/
void HbAbstractItemContainerPrivate::deleteItem(HbAbstractViewItem *item, bool animate)
{
    Q_Q(HbAbstractItemContainer);

    q->setItemTransientState(item->modelIndex(), item->transientState());
    mItems.removeOne(item);
    q->itemRemoved(item, animate);

    emit q->itemAboutToBeDeleted(item);

#ifndef HB_EFFECTS
    delete item;
#else
    if (!HbEffect::effectRunning(item, "disappear") 
        && !HbEffect::effectRunning(item, "collapse")) {
        delete item;
    }
#endif
}

/*
    \private
    The previous or the next index must be in the buffer. We cannot assume in this base class 
    that the container is a list we just have to loop through the items and find if the previous
    or next exist in the buffer. Next index should not be the first one in the buffer and the
    previous index should not be the last one in the buffer in order this index to the buffer.
*/
bool HbAbstractItemContainerPrivate::intoContainerBuffer(const QModelIndex &index) const
{    
    QModelIndex nextIndex = mItemView->modelIterator()->nextIndex(index);
    QModelIndex previousIndex = mItemView->modelIterator()->previousIndex(index);

    int itemCount = mItems.count();
    for (int i = 0; i < itemCount; ++i) {
        QModelIndex currentIndex = mItems.at(i)->modelIndex();
        if (currentIndex == nextIndex && i != 0){
            return true;
        } else if (currentIndex == previousIndex && i != (itemCount - 1)) {
            return true;
        }
    }
    return false;
} 

int HbAbstractItemContainerPrivate::containerBufferIndexForModelIndex(const QModelIndex &index) const
{   
    int bufferIndex = 0;
    QModelIndex nextIndex = mItemView->modelIterator()->nextIndex(index);
    QModelIndex previousIndex = mItemView->modelIterator()->previousIndex(index);

    while (bufferIndex < mItems.count()) {
        QModelIndex currentIndex = mItems.at(bufferIndex)->modelIndex();
        if (currentIndex == nextIndex) {
            break;
        }

        ++bufferIndex;

        if (currentIndex == previousIndex) {
            break;
        }
    }
    return bufferIndex;
}

void HbAbstractItemContainerPrivate::restoreItemPosition(HbAbstractViewItem *item, const QPointF &position)
{
    Q_Q(HbAbstractItemContainer);

    if (item) {
        QPointF delta = itemBoundingRect(item).topLeft() - position;
        if (!delta.isNull()) {
            q->setPos(q->pos() - delta);

            adjustContent();
        }
    }
}

void HbAbstractItemContainerPrivate::insertItem(HbAbstractViewItem *item, int pos, const QModelIndex &index, bool animate)
{
    Q_Q(HbAbstractItemContainer);

    if (item) {
        mItems.insert(pos, item);
        q->itemAdded(pos, item, animate);

        q->setItemModelIndex(item, index);
    }
}

qreal HbAbstractItemContainerPrivate::getDiffWithoutScrollareaCompensation(const QPointF &delta) const
{
    Q_Q( const HbAbstractItemContainer);
    const QSizeF containerSize(q->size());
    const QPointF containerPos(q->pos());
    qreal diff = 0.0;
    qreal invisibleArea = 0.0;
    if (delta.y() > 0) {
        // space at the bottom
        QSizeF viewSize = mItemView->size();
        invisibleArea = containerSize.height() - viewSize.height() + containerPos.y();
        if (invisibleArea < delta.y()) {
            diff = delta.y() - invisibleArea;
        }
    } else {
        // space at the top
        invisibleArea = -containerPos.y();
        if (containerPos.y() > delta.y()) {
            diff = delta.y() + invisibleArea;
        }
    }

    return diff;
}

/*!  
    Constructs a new HbAbstractItemContainer with \a parent.
*/
HbAbstractItemContainer::HbAbstractItemContainer(QGraphicsItem *parent) : 
    HbWidget(*new HbAbstractItemContainerPrivate(), parent) 
{
    Q_D(HbAbstractItemContainer);

    d->q_ptr = this;
    d->init();
}

/*!
    Constructs an item with private class object \a dd and \a parent. 
*/
HbAbstractItemContainer::HbAbstractItemContainer(HbAbstractItemContainerPrivate &dd, QGraphicsItem *parent) :
    HbWidget(dd, parent)
{
    Q_D(HbAbstractItemContainer);

    d->q_ptr = this;
    d->init();
}

/*!
    Destroys the container.
*/
HbAbstractItemContainer::~HbAbstractItemContainer()
{
}

/*!
    \reimp
*/
bool HbAbstractItemContainer::event(QEvent *e)
{
    if (e->type() == QEvent::LayoutRequest) {
        QGraphicsWidget *parentWid = parentWidget();
        if (!parentLayoutItem() 
            && parentWid) {
            HbApplication::postEvent(parentWid, new QEvent(QEvent::LayoutRequest));
        }
        Q_D(HbAbstractItemContainer);
        d->updateItemBuffer();
    } else if (e->type() == UpdateItemBufferEvent) {
        Q_D(HbAbstractItemContainer);
        d->updateItemBuffer();
    }

    return HbWidget::event(e);
}

/*!
    Returns view item object corresponding given model \a index. This might be 0 pointer if
    there is no view item representing given index or given \a index is invalid.
*/
HbAbstractViewItem* HbAbstractItemContainer::itemByIndex(const QModelIndex &index) const
{
    Q_D(const HbAbstractItemContainer);
    
    if (!index.isValid()) {
        return 0;
    }
    return d->item(index);
}

/*!
    This function is provided for convenience. 
    
    It removes all the items from the container and clears the internal state model.
*/
void HbAbstractItemContainer::removeItems()
{
    Q_D(HbAbstractItemContainer);

    qDeleteAll(d->mItems);
    d->mItems.clear();
    d->mItemStateList.clear();
    d->mItemStates.clear();

    emit itemAboutToBeDeleted(0);

}

/*!
    Returns the item view that container is connected.
*/
HbAbstractItemView *HbAbstractItemContainer::itemView() const
{
    Q_D(const HbAbstractItemContainer);
    return d->mItemView;
}

/*!
    Sets item \a view that container is connected.
*/
void HbAbstractItemContainer::setItemView(HbAbstractItemView *view)
{
    Q_D(HbAbstractItemContainer);

    if (view != d->mItemView) {
        if (d->mItemView) {
            d->mItemView->removeEventFilter(this);
        }

        d->mItemView = view;

        foreach (HbAbstractViewItem *prototype, d->mPrototypes) {
            d->initPrototype(prototype);
        }

        if (d->mItemView) {
            setParentItem(view);
            d->mItemView->installEventFilter(this);
        }
    }
}

/*!
    Assigns new model \a index to the given \a item. Item's current state is saved
    and state for \a index is restored to item.
*/
void HbAbstractItemContainer::setItemModelIndex(HbAbstractViewItem *item, const QModelIndex &index)
{

    if (item && item->modelIndex() != index) { 

        setItemTransientState(item->modelIndex(), item->transientState());

        // Transfer the state from item currently representing index to new item, if such exists.
        HbAbstractViewItem *oldItem = itemByIndex(index);

        if (oldItem) {
            item->setTransientState(oldItem->transientState());
        } else {
            item->setTransientState(itemTransientState(index));
        }


        item->setModelIndex(index);
    }
}

/*!
    Sets item's model indexes starting from given \a startIndex. If \a startIndex is
    QModelIndex() then startIndex is taken from the first item. 

    \note If there are not enough model indexes from \a startIndex to last model index 
    for all the items then QModelIndex() is assigned to rest of the view items.
*/
void HbAbstractItemContainer::setModelIndexes(const QModelIndex &startIndex)
{
    Q_D(HbAbstractItemContainer);

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

    QModelIndex index = startIndex;
    if (!index.isValid()) {
        if (!d->mItems.isEmpty()) {
            index = d->mItems.first()->modelIndex();
        } 
        
        if (!index.isValid()) {
            index = d->mItemView->modelIterator()->nextIndex(index);
        }
    }

    QModelIndexList indexList;

    int itemCount(d->mItems.count());

    if (itemCount != 0 && index.isValid()) {
        indexList.append(index);
    }

    for (int i = indexList.count(); i < itemCount; ++i) {
        index = d->mItemView->modelIterator()->nextIndex(indexList.last());

        if (index.isValid()) {
            indexList.append(index);
        } else {
            break;
        }
    }

    for (int i = indexList.count(); i < itemCount; ++i) {
        index = d->mItemView->modelIterator()->previousIndex(indexList.first());

        if (index.isValid()) {
            indexList.prepend(index);
        } else {
            break;
        }
    }

    // if items have been added/removed in the middle of the buffer, there might be items 
    // that can be reused at the end of the buffer. The following block will scan for 
    // those items and move them in correct position 
    int lastUsedItem = -1;
    for (int indexCounter = 0; indexCounter < indexList.count(); ++indexCounter) {
        HbAbstractViewItem *item = d->mItems.at(indexCounter);
        if (item && item->modelIndex() == indexList.at(indexCounter)) { 
            lastUsedItem = indexCounter;
        } else {
            for (int itemCounter = lastUsedItem + 1; itemCounter < d->mItems.count(); itemCounter++) {
                HbAbstractViewItem *item2 = d->mItems.at(itemCounter);
                if (item2->modelIndex() == indexList.at(indexCounter)) { 
                    d->mItems.swap(indexCounter, itemCounter);
                    itemRemoved(d->mItems.at(indexCounter), false);
                    itemRemoved(d->mItems.at(itemCounter), false);
                    itemAdded(qMin(indexCounter, itemCounter), d->mItems.at(qMin(indexCounter, itemCounter)), false);
                    itemAdded(qMax(indexCounter, itemCounter), d->mItems.at(qMax(indexCounter, itemCounter)), false);
                    lastUsedItem = itemCounter;
                    break;
                }
            }
        }
    }

    int indexCount(indexList.count());
    for (int i = 0; i < itemCount; ++i) {
        HbAbstractViewItem *item = d->mItems.at(i);
        HbAbstractViewItem *prototype = 0;
        // setItemModelIndex() is considered recycling. It is called only, if recycling is permitted
        if (i < indexCount) {
            prototype = d->itemPrototype(indexList.at(i));
        }
        if (prototype) {
            if (    prototype == item->prototype()
                &&  d->mItemRecycling) {
                setItemModelIndex(item, indexList.at(i));
            } else if (     d->mItemRecycling
                       ||   (   !d->mItemRecycling
                            &&  item->modelIndex() != indexList.at(i))) {
                d->deleteItem(item);
                insertItem(i, indexList.at(i));
            } // else: !d->mItemRecycling && item->modelIndex().isValid() == indexList.at(i))
        } else {
            d->deleteItem(item);
            itemCount--;
            i--;
        }
    }
}

/*!
    Returns list of items inside item buffer.
*/
QList<HbAbstractViewItem *> HbAbstractItemContainer::items() const
{
    Q_D(const HbAbstractItemContainer);
    return d->mItems;
}

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

    // Add new item if maximum item count allows it or item falls within the range of
    // item buffer items. 
    if (d->intoContainerBuffer(index)) {
        int bufferIndex = d->containerBufferIndexForModelIndex(index); 

        if (bufferIndex >= d->mItems.count()
            || d->mItems.at(bufferIndex)->modelIndex() != index) {
            // Store the second item position related to view.
            HbAbstractViewItem *referenceItem = d->mItems.value(1);
            QPointF referenceItemPos;
            if (referenceItem) {
                referenceItemPos = d->itemBoundingRect(referenceItem).topLeft();
            }

            HbAbstractViewItem *recycledItem = 0;
            QRectF viewRect = d->itemBoundingRect(d->mItemView);

            if (d->mItemRecycling && !viewRect.isEmpty()) {
                // Recycling allowed. Try recycling the items from buffer.
                int firstVisible = 0;
                int lastVisible = 0;
                d->firstAndLastVisibleBufferIndex(firstVisible, lastVisible, viewRect, false);

                int itemsOnTop = firstVisible - 1;
                int itemsOnBottom = d->mItems.count() - lastVisible - 1;

                if (itemsOnBottom > 0) {
                    recycledItem = d->mItems.takeLast();
                } else if (itemsOnTop > 0) {
                    recycledItem = d->mItems.takeFirst();
                    bufferIndex--;
                    bufferIndex = qMax(0, bufferIndex);
                }

                if (recycledItem) {
                    itemRemoved(recycledItem, false);
                    d->insertItem(recycledItem, bufferIndex, index, animate);
                }
            }

            if (!recycledItem) {
                // No recycling has happened. Insert completely new item.
                insertItem(bufferIndex, index, animate);
            }

            // Restore second item position.
            d->restoreItemPosition(referenceItem, referenceItemPos);
            
            if (!recycledItem && d->mItemRecycling) {
                // Resize the buffer.
                d->updateItemBuffer();
            }
        }
    } else if (d->mItems.count() < maxItemCount()) {
        d->updateItemBuffer();
    }
}

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

    HbAbstractViewItem *item = d->item(index);
    d->doRemoveItem(item, index, animate);
}

/*!
    Removes item from \a pos.
*/
void HbAbstractItemContainer::removeItem(int pos, bool animate)
{
    Q_D(HbAbstractItemContainer);

    Q_ASSERT(pos >= 0 && pos < d->mItems.count());        

    HbAbstractViewItem *item = d->mItems.at(pos);
    QModelIndex index = item->modelIndex();
    d->doRemoveItem(item, index, animate);
}

/*!
    Derived class should implement this function to perform item recycling based on container \a delta.
    Given \a delta is the distance between container's current position and desired new position. Recycling
    should be done based on the new position and function should return the actual delta. Actual delta could
    be modified value from \a delta. Delta modification can be useful e.g. to compensate the item changes
    that are caused by relayouting of items after changing the item position within layout.

    Default implementation does not recycle items and returns the \a delta unchanged.
*/
QPointF HbAbstractItemContainer::recycleItems(const QPointF &delta)
{
    return delta;
}

/*!
    \reimp
*/
bool HbAbstractItemContainer::eventFilter(QObject *obj, QEvent *event)
{
    Q_D(HbAbstractItemContainer);

    if (obj == d->mItemView) {
        switch (event->type()){
            case QEvent::GraphicsSceneResize: {
                viewResized(d->mItemView->size());
                d->updateItemBuffer();
                break;
            }
            default:
                break;
        }
    }

    return HbWidget::eventFilter(obj, event);
}

/*!
    Returns maximum amount of items that item buffer can hold. 

    Default implementation returns the total number of indexes that can
    be accessed on current view.

    Derived class that supports item recycling should define their own implementation
    for this function.

    \note Return value should not be more than HbAbstractItemView::indexCount()
*/
int HbAbstractItemContainer::maxItemCount() const
{    
    Q_D(const HbAbstractItemContainer);

    if (d->mItemView) {
        return d->mItemView->modelIterator()->indexCount();
    } else {
        return 0;
    }
}

/*!
    Deletes other prototypes and sets \a prototype as only prototype.

    Returns true if the prototype list was changed; otherwise returns false.
*/
bool HbAbstractItemContainer::setItemPrototype(HbAbstractViewItem *prototype)
{
    QList<HbAbstractViewItem *> prototypeList;
    prototypeList.append(prototype);
    return setItemPrototypes(prototypeList);
}

/*!
    Returns the list of item prototypes.
*/
QList<HbAbstractViewItem *> HbAbstractItemContainer::itemPrototypes() const
{
    Q_D(const HbAbstractItemContainer);
    
    if (d->mPrototypes.isEmpty()) {
        HbAbstractViewItem *defaultPrototype = createDefaultPrototype();
        if (defaultPrototype) {
            d->initPrototype(defaultPrototype);

            d->mPrototypes.append(defaultPrototype);
        }
    }

    return d->mPrototypes;
}

/*!
    Sets the list of prototypes. 
    
    Returns true if the prototype list was changed; otherwise returns false.
*/
bool HbAbstractItemContainer::setItemPrototypes(const QList<HbAbstractViewItem *> &prototypes)
{
    Q_D(HbAbstractItemContainer);
    
    bool changed = false;

    if (prototypes.count() > 0) {
        if (d->mPrototypes != prototypes) {
            foreach (HbAbstractViewItem *prototype, d->mPrototypes) {
                if (!prototypes.contains(prototype)) {
                    delete prototype;
                }
            }

            foreach (HbAbstractViewItem *prototype, prototypes) {
                if (!d->mPrototypes.contains(prototype)) {
                    d->initPrototype(prototype);
                }
            }
            changed = true;
        }

        d->mPrototypes = prototypes;
    }

    return changed;
    
}

/*!
    Returns transient state of view item with \a index.
*/
QHash<QString, QVariant> HbAbstractItemContainer::itemTransientState(const QModelIndex &index) const
{
    Q_D(const HbAbstractItemContainer);
    return d->mItemStates.value(index);
}

/*!
    This is an overloaded member function, provided for convenience.

    Stores \a key with \a value of a view item with \a index into state model.
    \a key is usually name of a Qt property. If \a value is invalid, state item with the \a key is removed.

    Default values of properties should not be added.
*/
void HbAbstractItemContainer::setItemTransientStateValue(const QModelIndex &index, const QString &key, const QVariant &value)
{
    Q_D(HbAbstractItemContainer);
    if (index.isValid()) {
        QHash<QString, QVariant> stateItem = d->mItemStates.value(index);
        if (!value.isValid()) {
            stateItem.remove(key);
        } else {
            stateItem.insert(key, value);
        }
        if (stateItem.count()) {
            d->mItemStates.insert(index, stateItem);
        } else {
            d->mItemStates.remove(index);
        }
    } else {
        d->mItemStates.remove(index);
    }
}


/*!
    Stores state of a view item with \a index into item state model. State of the view item is usually 
    retrieved by calling HbAbstractViewItem::transientState().
    
    Existing state is replaced. If \a state is empty, existing state is removed. 
    Default values of state items should not be added into \a state.

    \sa HbAbstractViewItem::transientState()
*/
void HbAbstractItemContainer::setItemTransientState(const QModelIndex &index, QHash<QString,QVariant> state)
{
    Q_D(HbAbstractItemContainer);
    if (index.isValid() && state.count()) {
        d->mItemStates.insert(index, state);
    } else {
        d->mItemStates.remove(index);
    }
}

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

/*!
    Returns the model indexes of items that are located on top left and bottom right corners
    of visible area.
*/
void HbAbstractItemContainer::firstAndLastVisibleModelIndex(
        QModelIndex& firstVisibleModelIndex,
        QModelIndex& lastVisibleModelIndex,
        bool fullyVisible) const
{
    Q_D(const HbAbstractItemContainer);

    QRectF viewRect(d->itemBoundingRect(d->mItemView));

    int firstVisibleBufferIndex( -1 );
    int lastVisibleBufferIndex( -1 );
    d->firstAndLastVisibleBufferIndex( firstVisibleBufferIndex, lastVisibleBufferIndex, viewRect, fullyVisible );
    if (firstVisibleBufferIndex != -1) {
        firstVisibleModelIndex = d->mItems.at(firstVisibleBufferIndex)->modelIndex();
    }
    if (lastVisibleBufferIndex != -1) {
        lastVisibleModelIndex = d->mItems.at(lastVisibleBufferIndex)->modelIndex();
    }
}

/*!
    Clears the state model.
*/
void HbAbstractItemContainer::removeItemTransientStates()
{
    Q_D(HbAbstractItemContainer);
    d->mItemStateList.clear();
    d->mItemStates.clear();
}

/*!
    Sets item recycling to \a enabled.
    By default recycling is off.
 */
void HbAbstractItemContainer::setItemRecycling(bool enabled)
{
    Q_D(HbAbstractItemContainer);
    if (d->mItemRecycling != enabled) {
        d->mItemRecycling = enabled;
        if (!enabled) {
            removeItemTransientStates();
        }

        d->updateItemBuffer();
    }
}

/*!
    Returns whether item recycling feature is in use.
 */
bool HbAbstractItemContainer::itemRecycling() const
{
    Q_D(const HbAbstractItemContainer);
    return d->mItemRecycling;
}

/*!
    Sets the feature informing whether all items in the item view have the same size.
    In case all the items have the same size, the item view can do some 
    optimizations for performance purposes.
*/
void HbAbstractItemContainer::setUniformItemSizes(bool enable)
{
    Q_D(HbAbstractItemContainer);
    d->mUniformItemSizes = enable;
}

/*!
    Returns whether the uniform item sizes feature is in use.
 */
bool HbAbstractItemContainer::uniformItemSizes() const
{
    Q_D(const HbAbstractItemContainer);
    return d->mUniformItemSizes;
}

/*!
    Inserts item for \a index to \a pos.
*/
void HbAbstractItemContainer::insertItem(int pos, const QModelIndex &index, bool animate)
{
    Q_D(HbAbstractItemContainer);
    HbAbstractViewItem *item = d->createItem(index);
    d->insertItem(item, pos, index, animate);
}

/*!
    Reset the internal state of the container.
*/
void HbAbstractItemContainer::reset()
{
    // position need to be reseted while changing model
    setPos(0.0, 0.0);
    removeItems(); 
    QCoreApplication::postEvent(this, new QEvent((QEvent::Type)UpdateItemBufferEvent));
}

/*!
*/
void HbAbstractItemContainer::resizeContainer()
{
    Q_D(HbAbstractItemContainer);

    QSizeF newSize = effectiveSizeHint(Qt::PreferredSize);

    if (d->mItemView) {
        Qt::Orientations scrollingDirections = d->mItemView->scrollDirections();

        if (!scrollingDirections.testFlag(Qt::Vertical)) {
            newSize.setHeight(d->mItemView->size().height());
        } 
        
        if (!scrollingDirections.testFlag(Qt::Horizontal)) {
            newSize.setWidth(d->mItemView->size().width());
        }
    }

    resize(newSize);
}

#include "moc_hbabstractitemcontainer_p.cpp"

