src/hbwidgets/itemviews/hbtumbleview.cpp
branchGCC_SURGE
changeset 15 f378acbc9cfb
parent 7 923ff622b8b9
child 21 4633027730f5
child 34 ed14f46c0e55
--- a/src/hbwidgets/itemviews/hbtumbleview.cpp	Thu Jul 15 14:03:49 2010 +0100
+++ b/src/hbwidgets/itemviews/hbtumbleview.cpp	Thu Jul 22 16:36:53 2010 +0100
@@ -35,12 +35,13 @@
 
 #include <QGraphicsSceneMouseEvent>
 #include <QStringListModel>
+#include <QItemSelectionModel>
+#include <QTapGesture>
 
 #define HB_TUMBLE_ITEM_ANIMATION_TIME 500
 #define HB_TUMBLE_PREFERRED_ITEMS 3
-#define DELAYED_SELECT_INTERVAL 100
 
-#define HBTUMBLE_DEBUG
+//#define HBTUMBLE_DEBUG
 #ifdef HBTUMBLE_DEBUG
 #include <QDebug>
 #endif
@@ -51,11 +52,14 @@
 {
     Q_DECLARE_PRIVATE(HbTumbleViewItemContainer)
 public:
-    HbTumbleViewItemContainer(QGraphicsItem* parent = 0);
+    HbTumbleViewItemContainer(QGraphicsItem* parent = 0);    
     QPointF recycleItems(const QPointF &delta);
 
     void setLoopingEnabled(bool looping) ;
     bool isLoopingEnabled() const ;
+    bool isLoopingNeeded() const;
+    void removeItem(const QModelIndex &index, bool animate );
+    void setModelIndexes(const QModelIndex &startIndex);
 };
 
 class HbTumbleViewItemContainerPrivate:public HbListItemContainerPrivate
@@ -66,6 +70,7 @@
     QPointF recycleItems(const QPointF &delta);
     HbAbstractViewItem *shiftDownItem(QPointF& delta);
     HbAbstractViewItem *shiftUpItem(QPointF& delta);
+    HbAbstractViewItem* item(const QModelIndex &index) const;
 
     bool mIsLooped;
 };
@@ -84,16 +89,14 @@
 
     void init(QAbstractItemModel *model);
     void calculateItemHeight();
+    void selectCurrentIndex(const QModelIndex& index);
 
     void selectMiddleItem();
+    HbAbstractViewItem* getCenterItem();
 
     void createPrimitives();
-
-    void delayedSelectCurrent(const QModelIndex& index);
-
     void _q_scrollingStarted();//private slot
     void _q_scrollingEnded();//private slot
-    void _q_delayedSelectCurrent();//private slot
 
     void setPressedState(HbAbstractViewItem *item);
 
@@ -127,73 +130,178 @@
 
 QPointF HbTumbleViewItemContainer::recycleItems(const QPointF &delta)
 {
-    Q_D(HbTumbleViewItemContainer);
-
-    if (d->mPrototypes.count() != 1) {
-        return delta;
-    }
-
-    QRectF viewRect(d->itemBoundingRect(d->mItemView));
-    viewRect.moveTopLeft(viewRect.topLeft() + delta);
-
-    int firstVisibleBufferIndex = -1;
-    int lastVisibleBufferIndex = -1;
-    d->firstAndLastVisibleBufferIndex(firstVisibleBufferIndex, lastVisibleBufferIndex, viewRect, false);
-
-    int hiddenAbove = firstVisibleBufferIndex;
-    int hiddenBelow = d->mItems.count() - lastVisibleBufferIndex - 1;
+    if(size().height() > itemView()->size().height()){
+            Q_D(HbTumbleViewItemContainer);
+            const qreal diff = d->getDiffWithoutScrollareaCompensation(delta);
+          
+            if(diff !=0.0){      
+                  QPointF newDelta(0.0, 0.0);
+                  qreal result = 0.0;
+                  HbAbstractViewItem *item = 0;
+                  if (diff < 0.0) {
+                        while (-newDelta.y() > diff) {
+                              item = d->shiftUpItem(newDelta);
+                              if (!item) {
+                                    break;
+                              }
+                        }
+                  }
 
-    if (d->mItems.count()
-        && (firstVisibleBufferIndex == -1 || lastVisibleBufferIndex == -1)) {
-        // All items out of sight.
-        if (d->itemBoundingRect(d->mItems.first()).top() < 0) {
-            // All items above view.
-            hiddenAbove = d->mItems.count();
-            hiddenBelow = 0;
-        } else {
-            // All items below view.
-            hiddenAbove = 0;
-            hiddenBelow = d->mItems.count();
-        }
-    }
-
-    QPointF newDelta(delta);
+                  else {
+                        while (-newDelta.y() < diff) {
+                              item = d->shiftDownItem(newDelta);
+                              if (!item) {
+                                    break;
+                              }
+                        }
+                  }
 
-    while (hiddenAbove > hiddenBelow + 1) {
-        HbAbstractViewItem *item = d->shiftDownItem(newDelta);
-        if (!item){
-            break;
-        }
-
-        if (!d->visible(item, viewRect)) {
-            hiddenBelow++;
-        }
-        hiddenAbove--;
-    }
+                  result = newDelta.y();
+                  return QPointF(delta.x(),delta.y()+result);
+            }
+      }
 
-    while (hiddenBelow > hiddenAbove + 1) {
-        HbAbstractViewItem *item = d->shiftUpItem(newDelta);
-        if (!item) {
-            break;
-        }
-
-        if (!d->visible( item, viewRect)) {
-            hiddenAbove++;
-        }
-        hiddenBelow--;
-    }
-
-    return newDelta;
+    return delta;
 }
 void HbTumbleViewItemContainer::setLoopingEnabled(bool looped) {
     Q_D(HbTumbleViewItemContainer);
-    d->mIsLooped = looped;
+    d->mIsLooped = looped;    
+
+    d->mItemView->scrollTo(d->mItemView->currentIndex(),HbAbstractItemView::PositionAtCenter);
+    recycleItems(QPointF());
 }
 bool HbTumbleViewItemContainer::isLoopingEnabled() const {
     Q_D(const HbTumbleViewItemContainer);
     return d->mIsLooped;
 }
 
+bool HbTumbleViewItemContainer::isLoopingNeeded() const
+{
+    Q_D(const HbTumbleViewItemContainer);
+      return (isLoopingEnabled() && (d->mItems.count() < maxItemCount()));
+}
+
+void HbTumbleViewItemContainer::removeItem(const QModelIndex &index, bool animate )
+{
+    Q_D(HbTumbleViewItemContainer);
+    HbListItemContainer::removeItem(index,animate);
+    if (d->mItems.count() < maxItemCount()) {
+        d->updateItemBuffer();
+    }
+}
+
+void HbTumbleViewItemContainer::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;
+        }
+    }
+    if(isLoopingEnabled() && indexList.count()<itemCount){
+        QModelIndex firstModelIndex =  d->mItemView->modelIterator()->index(0,QModelIndex());
+        indexList.append(firstModelIndex);
+        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--;
+        }
+    }
+}
+
 HbTumbleViewItemContainerPrivate::HbTumbleViewItemContainerPrivate()
     : mIsLooped(false) 
 { 
@@ -264,6 +372,17 @@
     return item;
 }
 
+HbAbstractViewItem *HbTumbleViewItemContainerPrivate::item(const QModelIndex &index) const
+{
+    int itemCount = mItems.count();
+    for(int i=0;i<itemCount;++i) {
+        if(mItems.at(i)->modelIndex() == index) {
+            return mItems.at(i);
+        }
+    }
+    return 0;
+}
+
 HbTumbleViewPrivate::HbTumbleViewPrivate()
     :HbListViewPrivate()
     ,mHeight(10.0)
@@ -314,13 +433,15 @@
     q->setVerticalScrollBarPolicy(HbScrollArea::ScrollBarAlwaysOff);
     q->setHorizontalScrollBarPolicy(HbScrollArea::ScrollBarAlwaysOff);
     q->setFrictionEnabled(true);
+
+    //don't want this to occupy entire screen. preferred is few items.
+    q->setSizePolicy(QSizePolicy::Preferred,QSizePolicy::Preferred);
+
     mDelayedSelectTimer.setSingleShot(true);
     bool b = q->connect(q,SIGNAL(scrollingStarted()),q,SLOT(_q_scrollingStarted()));
     Q_ASSERT(b);
     b = q->connect(q,SIGNAL(scrollingEnded()),q,SLOT(_q_scrollingEnded()));
     Q_ASSERT(b);
-    b = q->connect(&mDelayedSelectTimer,SIGNAL(timeout()),q,SLOT(_q_delayedSelectCurrent()));
-    Q_UNUSED(b);
     createPrimitives();
 }
 
@@ -331,17 +452,27 @@
     if(!q->scene()) {
         return;
     }
-    QPointF centerPt = q->mapToScene(q->boundingRect().center());
-    HbAbstractViewItem *item = itemAt(centerPt);
+    HbAbstractViewItem *item = getCenterItem();
 
     if(item) {
 #ifdef HBTUMBLE_DEBUG  
-    qDebug() << "HbTumbleViewPrivate::selectMiddleItem - " << item->modelIndex().row() ;
+        qDebug() << "HbTumbleViewPrivate::selectMiddleItem - " << item->modelIndex().row() ;
 #endif
-            delayedSelectCurrent(item->modelIndex());
-            mSelected = item->modelIndex().row();
-        }
+        selectCurrentIndex(item->modelIndex());
+        mSelected = item->modelIndex().row();
     }
+}
+
+HbAbstractViewItem* HbTumbleViewPrivate::getCenterItem()
+{
+    Q_Q(HbTumbleView);
+
+    if(!q->scene()) {
+        return 0;
+    }
+    QPointF centerPt = q->mapToScene(q->boundingRect().center());
+    return itemAt(centerPt);
+}
 
 void HbTumbleViewPrivate::scrollTo(const QModelIndex &index, HbAbstractItemView::ScrollHint hint)
 {
@@ -366,12 +497,74 @@
 #endif
 }
 
-void HbTumbleView::scrollTo(const QModelIndex &index, ScrollHint)
+void HbTumbleViewPrivate::selectCurrentIndex(const QModelIndex& index)
 {
-#ifdef HBTUMBLE_DEBUG  
-    qDebug() << "HbTumbleView::scrollTo(" << index.row() << ", )";
+    Q_Q(HbTumbleView);
+    if(!mIsAnimating && !mIsScrolling) {
+        if(index == q->currentIndex()){
+            HbAbstractViewItem *item =q->itemByIndex(index);
+            QPointF delta = pixelsToScroll(item,HbAbstractItemView::PositionAtCenter );
+            QPointF newPos = -mContainer->pos() + delta;
+            checkBoundaries(newPos);
+            scrollByAmount(newPos - (-mContents->pos()));
+            mIsScrolling = false;
+        }
+        else{
+            q->setCurrentIndex(index,QItemSelectionModel::SelectCurrent);
+        }
+    }
+}
+
+void HbTumbleView::scrollTo(const QModelIndex &index, ScrollHint hint)
+{    
+    Q_D(HbTumbleView);
+
+    if (!d->mModelIterator->model()
+        ||  index.model() != d->mModelIterator->model()) {
+            return;
+    }
+
+    //If item is in the buffer, just reveal it.
+    //This is always the case if recycling is off
+    //and sometimes the case when recycling is on
+    if (itemRecycling()) {
+        if (    !d->mContainer->itemByIndex(index)
+            ||  hint != EnsureVisible) {
+                //Now the item is not in the buffer.
+                //We must first set the item to be in the buffer
+                //If the item is above let's put it first and if it is below put it last
+
+                int newIndex = -1;
+
+                switch (hint) {
+            case PositionAtCenter: 
+                {
+                    int containerCount = d->mContainer->items().count();
+                    newIndex = index.row() - containerCount / 2 ;
+                    if(newIndex < 0){
+                        if(((HbTumbleViewItemContainer*)(d->mContainer))->isLoopingNeeded()){
+                            newIndex = d->mModelIterator->indexCount()+newIndex;
+                        }
+                        else{
+                            newIndex = 0;
+                        }
+                    }
+                    break;
+                }
+
+            case EnsureVisible:
+            case PositionAtTop:
+            case PositionAtBottom:
+            default: {
+#ifdef HBTUMBLE_DEBUG
+                qWarning()<<"Scroll Hint is not supported "; 
 #endif
-    HbListView::scrollTo(index, PositionAtCenter);
+                     }
+                }
+                d->mContainer->setModelIndexes(d->mModelIterator->index(newIndex));
+        }
+    }
+    HbAbstractItemView::scrollTo(index, hint);
 }
 
 void HbTumbleViewPrivate::createPrimitives()
@@ -419,21 +612,25 @@
 /*!
     @proto
     \class HbTumbleView 
-    \this is a tumbler widget which lets the user select alphanumeric values from a predefined list of 
-    values via vertical flicking and dragging. Typically widgets such as date picker and time picker use the 
+    \brief HbTumbleView is a tumbler widget which lets the user select alphanumeric values from a predefined list of values via vertical flicking and dragging.<br> 
+
+    Typically widgets such as date picker and time picker use the 
     Tumbler. The Tumbler could also be used to change other values such as number code sequence, 
-    landmark coordinates, country selection, and currency.
+    landmark coordinates, country selection, and currency.<br>
 
     Only strings can be accepted as HbTumbleView's items.
 
-    \this can be used like this:
+    HbTumbleView can be used, as shown in the below code snippet:
     \snippet{ultimatecodesnippet/ultimatecodesnippet.cpp,52}
+
+    \image html hbdatetimepicker_date.png  "Two TumbleViews(tumblers) in a datetime picker widget in d/MMMM format. One tumbler for day and the other for month."
+    <b>Note:</b>Graphics in the above image varies depending on theme.
 */
 
 /*!
     \fn void itemSelected(int index)
 
-    This signal is emitted when an item is selected in date time picker.
+    This signal is emitted when an item is selected in the tumbler.
     \param index  selected item.
 
 */
@@ -457,7 +654,7 @@
 /*!
     HbTumbleView's constructor.
 
-    \param list to be set as data to QStringListModel.
+    \param list String list to be set as data to HbTumbleView's model.
     \parent item to set as parent.
 */
 HbTumbleView::HbTumbleView(const QStringList &list,QGraphicsItem *parent)
@@ -497,7 +694,7 @@
 /*!
     Sets the HbTumbleView's items to the given string \a list.
 
-    \param list Items to be set as tumble view's model.
+    \param list Items to be set as data to HbTumbleView's model.
 
 */
 void HbTumbleView::setItems(const QStringList &list)
@@ -514,7 +711,7 @@
 /*!
     Returns items in QStringList format.
 
-    \return list of items in tumbleview's model in QStringList format.
+    \return List of items set as data to HbTumbleView's model in QStringList format.
 */
 QStringList HbTumbleView::items() const
 {
@@ -528,7 +725,7 @@
 /*!
     Sets the selection to the item at \a index.
 
-    \param index to be selected in the tumble view.
+    \param index of the item to be selected in the tumbler.
 
 */
 void HbTumbleView::setSelected(int index)
@@ -541,7 +738,7 @@
 
     QModelIndex modelIndex = d->mModelIterator->index(index, rootIndex());
     if(modelIndex.isValid()) {
-        d->delayedSelectCurrent(modelIndex);
+        d->selectCurrentIndex(modelIndex);
         emitActivated(modelIndex);
     } 
 }
@@ -549,7 +746,7 @@
 /*!
     Returns the index of the current selected item in integer format.
 
-    \return current index selected in tumble view.
+    \return current index of the item selected in the tumbler.
 */
 int HbTumbleView::selected() const
 {
@@ -557,7 +754,6 @@
 }
 
 /*!
-
     \deprecated HbTumbleView::primitive(HbStyle::Primitive)
         is deprecated.
 
@@ -653,25 +849,17 @@
 */
 void HbTumbleView::rowsInserted(const QModelIndex &parent,int start,int end)
 {
+    Q_D(HbTumbleView);
     HbListView::rowsInserted(parent,start,end);
-    scrollTo(currentIndex(),PositionAtCenter);
-}
 
-void HbTumbleViewPrivate::_q_delayedSelectCurrent()
-{
-    Q_Q(HbTumbleView);
-    if(!mIsAnimating && !mIsScrolling) {
-         if(mDelayedSelectIndex == q->currentIndex()){
-             HbAbstractViewItem *item =q->itemByIndex(mDelayedSelectIndex);
-             QPointF delta = pixelsToScroll(item,HbAbstractItemView::PositionAtCenter );
-             QPointF newPos = -mContainer->pos() + delta;
-             checkBoundaries(newPos);
-             scrollByAmount(newPos - (-mContents->pos()));
-         }
-         else{
-            q->setCurrentIndex(mDelayedSelectIndex);
-        }
+    //Trigger recycle, in the scenario where item's are deleted and reinserted again.
+    QPointF alignedPosition = d->mContents->pos();
+    Q_UNUSED(alignedPosition);
+    if(alignedPosition.y() > 0.0){
+        alignedPosition.setY(0.0);
+        d->HbScrollAreaPrivate::setContentPosition(alignedPosition);
     }
+    scrollTo(currentIndex(),PositionAtCenter);
 }
 
 /*!
@@ -727,9 +915,9 @@
 }
 
 /*!
-    Sets the looping enabled flag to eith true or false, which makes the tumbleview to scroll in a circular way.
+    Sets the looping enabled flag to either true or false, which makes the tumbler to scroll in a circular way.
 
-    \param looped flag to enable curcular view.
+    \param looped flag to enable curcular view for the tumbler.
 
     \sa isLoopingEnabled
 */
@@ -745,7 +933,7 @@
 /*!
     Returns looping enabled flag.
 
-    \return looping enabled flag.
+    \return looping enabled flag as boolean value.
 
     \sa setLoopingEnabled
 
@@ -792,7 +980,7 @@
     QSizeF sh=HbListView::sizeHint(which,constraint);
     switch(which) {
         case Qt::MinimumSize:
-			sh = QSizeF(sh.width(),HB_TUMBLE_PREFERRED_ITEMS*d->mHeight);
+            sh = QSizeF(sh.width(),HB_TUMBLE_PREFERRED_ITEMS*d->mHeight);
             break;
         case Qt::PreferredSize:
             sh = QSizeF(sh.width(),HB_TUMBLE_PREFERRED_ITEMS*d->mHeight);
@@ -833,13 +1021,12 @@
         return;
     }
 
-    QPointF pt = q->mapToScene(q->boundingRect().center());
-    HbAbstractViewItem *centerItem=itemAt(pt);
+    HbAbstractViewItem *centerItem=getCenterItem();
     if(centerItem) {
         setPressedState(centerItem);
 
         if(centerItem->modelIndex().isValid()) {
-            delayedSelectCurrent(centerItem->modelIndex());
+            selectCurrentIndex(centerItem->modelIndex());
             q->emitActivated(centerItem->modelIndex());
         } 
     }
@@ -878,10 +1065,60 @@
     HbListView::rowsRemoved(parent,start,end);
     scrollTo(currentIndex(),PositionAtCenter);
 }
-void HbTumbleViewPrivate::delayedSelectCurrent(const QModelIndex& index)
+
+/*!
+    \reimp
+*/
+void HbTumbleView::dataChanged(const QModelIndex &topLeft, const QModelIndex &bottomRight)
+{
+    Q_D(const HbListView);
+
+    QList<HbAbstractViewItem *> items = d->mContainer->items();
+    if (!items.isEmpty()) {
+        QModelIndex rootIndex = d->mModelIterator->rootIndex();
+        QModelIndex firstIndex = items.first()->modelIndex();
+        QModelIndex lastIndex = items.last()->modelIndex();
+
+        if ( topLeft.parent() == rootIndex
+            /*&& firstIndex.row() <= bottomRight.row()
+            && topLeft.row() <= lastIndex.row()*/) {
+            HbAbstractItemView::dataChanged(topLeft, bottomRight);
+        }
+    }
+}
+
+void HbTumbleView::gestureEvent(QGestureEvent *event)
 {
-    mDelayedSelectIndex = index;
-    mDelayedSelectTimer.start(DELAYED_SELECT_INTERVAL);
+    Q_D(HbTumbleView);
+    if(QTapGesture *gesture = static_cast<QTapGesture*>(event->gesture(Qt::TapGesture))){
+        switch(gesture->state()){
+            case Qt::GestureStarted:
+                if(d->mIsAnimating || d->mIsScrolling){
+                    d->mInternalScroll = true;
+                    HbAbstractViewItem* centerItem = d->getCenterItem();
+                    if(centerItem){
+                        d->mDelayedSelectIndex = centerItem->modelIndex();
+                    }
+                }
+                else{
+                    d->mDelayedSelectIndex = QModelIndex();
+                }
+                break;
+            case Qt::GestureCanceled:
+                d->mInternalScroll = false;
+                break;
+            case Qt::GestureFinished:
+                d->mInternalScroll = false;
+                if(d->mDelayedSelectIndex.isValid()){
+                    d->selectCurrentIndex(d->mDelayedSelectIndex);
+                }
+                break;
+            default:
+                break;
+
+        }
+    }
+    HbListView::gestureEvent(event);
 }
 
 #include "moc_hbtumbleview.cpp"