src/hbwidgets/itemviews/hblistview_p.cpp
author Dremov Kirill (Nokia-D-MSW/Tampere) <kirill.dremov@nokia.com>
Mon, 04 Oct 2010 00:38:12 +0300
changeset 30 80e4d18b72f5
parent 23 e6ad4ef83b23
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 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 "hblistview_p.h"
#include "hblistview.h"
#include "hbscrollbar.h"

#include "hblistviewitem.h"
#include "hbabstractitemcontainer_p.h"
#include "hblistitemcontainer_p.h"
#include <hbwidgetfeedback.h>
#include "hbmodeliterator.h"
#include <hbpangesture.h>

#include <QGraphicsScene>

namespace {
    static const int FLICKMINDISTANCE = 50;
    static const qreal SCROLLSPEED_FACTOR = 0.0004;
    static const qreal FLICK_TIMEOUT = 200;
    static const qreal DRAGGED_ITEM_SCROLL_SPEED = 0.2;
    static const qreal DRAGGED_ITEM_OPACITY = 0.75;
    const QString KDefaultLayoutOption = "default";
}

HbListViewPrivate::HbListViewPrivate() :
    HbAbstractItemViewPrivate(),
    mArrangeMode(false),
    mDraggedItemIndex(),
    mDraggedItem(0),
    mMousePressTimer(),
    mMousePressPos(),
    mScrollStartMousePos(),
    mLastScrollPos(),
    mOriginalTransform(),
    mMoveOngoing(false)
{
}

HbListViewPrivate::~HbListViewPrivate()
{
}

void HbListViewPrivate::init()
{
    Q_Q(HbListView);
    q->setItemRecycling(true);
    mLayoutOptionName = KDefaultLayoutOption;
}

void HbListViewPrivate::moveDraggedItemTo(const QPointF &mousePosition)
{
    Q_Q(HbListView);

    if(!mDraggedItem) {
        return;
    }

    mDraggedItem->setOpacity(DRAGGED_ITEM_OPACITY);

    // find the list item under the cursor. The basic itemAt is not working
    // here as we have the draggeditem always first. So the graphicsitems are
    // iterated here to find a HbAbstractViewItem that is not the draggeditem
    HbAbstractViewItem *itemUnder = 0;
    QList<QGraphicsItem *> items = q->scene()->items(mousePosition);
    int count = items.count();
    for (int current = count - 1; current >= 0; --current) {
        QGraphicsItem *i = items.at(current);
        itemUnder = viewItem(i);
        if (itemUnder && itemUnder != mDraggedItem && itemUnder->itemView() == q) {
            break;
        }
    }

    if (itemUnder) {
        QModelIndex targetIndex;
        if (mDraggedItemIndex.row() > itemUnder->modelIndex().row() + 1 
            || mDraggedItemIndex.row() < itemUnder->modelIndex().row() - 1 ) {
                // item is far away from the original place, must move
                targetIndex = itemUnder->modelIndex();
        } else if (mDraggedItemIndex.row() < itemUnder->modelIndex().row()) {
            // moving down if drag position over the next item
            if (mDraggedItem->scenePos().y() > itemUnder->scenePos().y()) {
                targetIndex = itemUnder->modelIndex();
            }
        } else {
            // moving up if drag position over the next item
            if (mDraggedItem->scenePos().y() < itemUnder->scenePos().y()) {
                targetIndex = itemUnder->modelIndex();
            } 
        }

        if (targetIndex.isValid()) {
            // reached a droppable spot
            HbWidgetFeedback::triggered(mDraggedItem,Hb::InstantDraggedOver);

            QPointF itemPos = itemBoundingRect(mDraggedItem).topLeft();

            mDraggedItem->setOpacity(1.0);
            mDraggedItem->setTransform(mOriginalTransform);
            mDraggedItem->setZValue(mDraggedItem->zValue() - 1);
            //mDraggedItem->setPressed(false, false);

            int targetRow = targetIndex.row();
            int startRow = qMin(targetRow, mDraggedItemIndex.row());
            int endRow = qMax(targetRow, mDraggedItemIndex.row());
            int containerStartIndex = mContainer->items().at(0)->modelIndex().row();
            mMoveOngoing = true;
            q->move(mDraggedItemIndex, targetIndex);
            static_cast <HbListItemContainer*>(mContainer)->setItemModelIndexes(startRow - containerStartIndex,
                                                                                startRow,
                                                                                endRow - startRow);
            mMoveOngoing = false;
           
            // in the swap the dragged item may have been deleted and recreated. So take it again here
            mDraggedItemIndex = mModelIterator->index(targetRow);
            if (mDraggedItemIndex.isValid()) {
                mDraggedItem = q->itemByIndex(mDraggedItemIndex);
                if (mDraggedItem) {
                    mDraggedItem->setTransform(mOriginalTransform);

                    // Keep the same visual position.
                    QPointF delta = itemPos - itemBoundingRect(mDraggedItem).topLeft();
                    mDraggedItem->translate(delta.x(), delta.y());

                    mDraggedItem->setOpacity(DRAGGED_ITEM_OPACITY);
                    mDraggedItem->setZValue(mDraggedItem->zValue() + 1);

                    //mDraggedItem->setPressed(true, false);

                    q->setCurrentIndex(mDraggedItemIndex);
                }
            }
        }
    }
}

bool HbListViewPrivate::panTriggered(QGestureEvent *event)
{
    Q_Q(HbListView);

    bool arrangeModeEnabled = false;
    if (mArrangeMode && mSelectionMode == HbAbstractItemView::NoSelection) {
        arrangeModeEnabled = true;
    }

    HbPanGesture *gesture = static_cast<HbPanGesture *>(event->gesture(Qt::PanGesture));
    switch (gesture->state()) {
        case Qt::GestureStarted: {
            if (arrangeModeEnabled && !mDraggedItem) {
                QPointF scenePos = event->mapToGraphicsScene(gesture->startPos());
                mDraggedItem = itemAt(scenePos);
                if (mDraggedItem) {
                    mDraggedItemIndex = mDraggedItem->modelIndex();

                    if (mDraggedItemIndex.isValid()) {
                        HbEffect::cancel(mDraggedItem, QString(), true);
                        mMousePressTimer.restart();
                        mMousePressPos = scenePos;
                        mOriginalTransform = mDraggedItem->transform();
                        mDraggedItem->setZValue(mDraggedItem->zValue() + 1);
                        mDraggedItem->translate(0, gesture->offset().y());

                        QObject::connect(q, SIGNAL(scrollPositionChanged(QPointF)), q, SLOT(scrolling(QPointF)));    
                    } else {
                        mDraggedItem = 0;
                    }
                }
                return true;
            }
        }
        case Qt::GestureUpdated: {
            if (arrangeModeEnabled && mDraggedItem) {
                QPointF scenePos = event->mapToGraphicsScene(gesture->hotSpot());
                QPointF pos = q->mapFromScene(scenePos);

                if (!q->isScrolling()) {
                    // move the item with the cursor to indicate the move
                    mDraggedItem->translate(0, event->mapToGraphicsScene(gesture->offset()).y() -
                                               event->mapToGraphicsScene(gesture->lastOffset()).y());

                    if (mMousePressTimer.elapsed() >= FLICK_TIMEOUT) {
                        moveDraggedItemTo(scenePos);
                    }
                }

                // in case we are "dragging" an item and at the top/bottom of
                // the view the view is scrolled to reveal more items in
                // that direction
                QModelIndex firstVisible;
                QModelIndex lastVisible;
                mContainer->firstAndLastVisibleModelIndex(firstVisible, lastVisible);
                if (firstVisible.isValid() && lastVisible.isValid()) {
                    // above indexes are valid so container contain at least one item - so it is
                    // safe to call first and last
                    QModelIndex firstItemIndex = mContainer->items().first()->modelIndex();
                    QModelIndex lastItemIndex = mContainer->items().last()->modelIndex();
                    // If the item is dragged up in the list (and there are more items to show), scroll up

                    if (!mIsAnimating
                        && !(visible(mContainer->itemByIndex(firstItemIndex), true) 
                             && (firstItemIndex.row() == 0))
                        && scenePos.y() < mMousePressPos.y()
                        && pos.y() < q->itemByIndex(firstVisible)->size().height()) {
                        mScrollStartMousePos = scenePos;
                        mLastScrollPos = QPointF(0,0);
                        animateScroll(QPointF(0.0f , DRAGGED_ITEM_SCROLL_SPEED));
                    }
                    // If the item is dragged down in the list (and there are more items to show), scroll down
                    else if (!mIsAnimating
                               && !(visible(mContainer->itemByIndex(lastItemIndex), true) 
                                    && (lastItemIndex.row() == mModelIterator->indexCount() - 1))
                               && scenePos.y() > mMousePressPos.y()
                               && pos.y() > (q->size().height() - q->itemByIndex(lastVisible)->size().height())) {
                        mScrollStartMousePos = scenePos;
                        mLastScrollPos = QPointF(0,0);
                        animateScroll(QPointF(0.0f , (-1 * DRAGGED_ITEM_SCROLL_SPEED)));
                    }
                    // If the view is scrolling and the drag event is inside the view, we need to stop the scrolling
                    else if (pos.y() < (q->size().height() - q->itemByIndex(lastVisible)->size().height())
                               && pos.y() > q->itemByIndex(firstVisible)->size().height()
                               && q->isScrolling()) {
                        stopAnimating();
                    }
                }
                return true;
            }
        }
        case Qt::GestureFinished: 
        case Qt::GestureCanceled: {
            if (arrangeModeEnabled && mDraggedItem) {
                QObject::disconnect(q, SIGNAL(scrollPositionChanged(QPointF)), q, SLOT(scrolling(QPointF)));

                if (q->isScrolling()) {
                    stopAnimating();
                }

                // remove item's drag indications
                mDraggedItem->setOpacity(1.0);
                mDraggedItem->setTransform(mOriginalTransform);
                mDraggedItem->setZValue(mDraggedItem->zValue() - 1);
                //mDraggedItem->setPressed(false);

                QPointF scenePos = event->mapToGraphicsScene(gesture->hotSpot());
                if (itemAt(scenePos)) {
                    int downTime = mMousePressTimer.elapsed();
                    // this seems to be a flick rather than item move, so start 
                    // scrolling
                    qreal distance = scenePos.y() - mMousePressPos.y();
                    if (downTime > 0 && downTime < FLICK_TIMEOUT 
                        && qAbs(distance) > FLICKMINDISTANCE ) {
                        animateScroll(QPointF (0.0f, (distance * 1000 / downTime) * SCROLLSPEED_FACTOR));
                    }
                    else {
                        // drop dragged item
                        HbWidgetFeedback::triggered(mDraggedItem,Hb::InstantReleased);
                    }
                }

                mDraggedItem = 0;
                return true;
            }
        }
        default:
            break;
    }

    return HbAbstractItemViewPrivate::panTriggered(event);
}

void HbListViewPrivate::arrangeModeSetup(bool newMode)
{
    Q_Q(HbListView);

    if (newMode) {
        mOriginalInteractiveScrollBar = q->verticalScrollBar()->isInteractive();
        q->verticalScrollBar()->setInteractive(true);
        mOriginalLongPressEnabled = q->longPressEnabled();
        q->setLongPressEnabled(false);
        mOriginalFriction = mFrictionEnabled;
        q->setFrictionEnabled(false);
    } else {
        q->verticalScrollBar()->setInteractive(mOriginalInteractiveScrollBar);
        q->setLongPressEnabled(mOriginalLongPressEnabled);
        q->setFrictionEnabled(mOriginalFriction);
    }

    mArrangeMode = newMode;
        
}