diff -r 000000000000 -r 16d8024aca5e src/hbwidgets/itemviews/hblistview.cpp --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/hbwidgets/itemviews/hblistview.cpp Mon Apr 19 14:02:13 2010 +0300 @@ -0,0 +1,592 @@ +/**************************************************************************** +** +** 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.h" +#include "hblistview_p.h" + +#include "hblistlayout_p.h" +#include "hblistviewitem.h" +#include "hblistitemcontainer_p.h" +#include "hblistitemcontainer_p.h" +#include "hbgesturefilter.h" +#include "hbscrollbar.h" +#include +#include "hbmodeliterator.h" + +#include +#include +#include + +#include +/*! + @beta + @hbwidgets + \class HbListView + \brief HbListView represents a list + + The list view is derived from QGraphicsWidget and can thus be embedded to + other QGraphicsLayout based layouts or other widgets. + + HbListView uses the model view design pattern. The model can be any QAbstractItemModel + based model. + + \snippet{ultimatecodesnippet/ultimatecodesnippet.cpp,9} + + Each list view item is represented by an instance of HbListViewItem. HbListView + uses HbListViewItem prototype to instantiate the list view items. HbListViewItem + can be subclassed for customization purposes. + Setting own prototype can be made like this: + + \snippet{ultimatecodesnippet/ultimatecodesnippet.cpp,10} + + By default, HbListView uses list view item recycling. This means that only the + visible list view items plus a small buffer of items above and below the visible + list is instantiated at a time. When the list is scrolled the list view items are + recycled so that the buffer size above and below the list is kept constant. + + View item layout + + View item layout configuration happens from HbListViewItem prototype that can be accesses with HbListViewItem::listItemPrototype function. + + More information about this can be found from HbListViewItem documentation. +*/ + +static const qreal DRAGGED_ITEM_SCROLL_SPEED = 0.2; +static const int FLICKMINDISTANCE = 50; +static const qreal FLICK_TIMEOUT = 200; +static const qreal SCROLLSPEED_FACTOR = 0.0004; + +/*! + Constructs a list view with \a parent. + */ +HbListView::HbListView(QGraphicsItem *parent) + : HbAbstractItemView( *new HbListViewPrivate, new HbListItemContainer, new HbModelIterator(), parent) +{ + Q_D( HbListView ); + d->q_ptr = this; + + d->init(); +} + +/*! + Constructs a list view with a private class object \a dd, + \a container and \a parent. + */ +HbListView::HbListView( HbListViewPrivate &dd, HbAbstractItemContainer *container, QGraphicsItem * parent ) + : HbAbstractItemView(dd, container, new HbModelIterator(), parent) +{ + Q_D( HbListView ); + d->q_ptr = this; + + d->init(); +} + + +/*! + Destructs the list view. + */ +HbListView::~HbListView() +{ +} + +/* + Returns the first item prototype from the list of item prototypes + after trying to convert it as HbListViewItem. + + This function is provided for convenience. +*/ +HbListViewItem *HbListView::listItemPrototype() const +{ + Q_D(const HbListView); + return qobject_cast(d->mContainer->itemPrototypes().first()); +} + + +/* + Scrolls the view if necessary to ensure that the item at \a index is visible. +*/ +void HbListView::scrollTo(const QModelIndex &index, ScrollHint hint) +{ + Q_D(HbListView); + + 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 == PositionAtTop + || 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 PositionAtTop: { + newIndex = index.row(); + break; + } + case PositionAtBottom: { + int containerCount = d->mContainer->items().count(); + newIndex = qMax(0,index.row() - containerCount + 1); + break; + } + case PositionAtCenter: { + int containerCount = d->mContainer->items().count(); + newIndex = qMax(0,index.row() - containerCount / 2 ); + break; + } + case EnsureVisible: + default: { + int containerCount = d->mContainer->items().count(); + if (containerCount + && index < d->mContainer->items().first()->modelIndex()){ + newIndex = index.row(); + } + else { + newIndex = qMax(0,index.row() - containerCount + 1); + } + break; + } + } + d->mContainer->setModelIndexes(d->mModelIterator->index(newIndex)); + } + } + HbAbstractItemView::scrollTo(index, hint); +} + +/*! + Returns the view item representing \a row. This can be NULL if + recycling is active and none of the loaded view items is representing + given row. +*/ +HbAbstractViewItem *HbListView::viewItem(int row) const +{ + Q_D(const HbListView); + + if (!d->mModelIterator->model()) { + return 0; + } + + return d->mContainer->itemByIndex(d->mModelIterator->index(row)); +} + +/*! + \reimp +*/ +int HbListView::type() const +{ + return Type; +} + +/*! + Returns the current arrange mode + */ +bool HbListView::arrangeMode() const +{ + Q_D(const HbListView); + return d->mArrangeMode; +} + +/*! + * Returns the view item being dragged. This is NULL if no item is being dragged. + */ +HbAbstractViewItem *HbListView::draggedItem() const +{ + Q_D( const HbListView ); + + return d->mDraggedItem; +} + +/*! + Tries to change the arrageMode. ArrangeMode is the mode where drag and drop + for arranging the items inside the list is supported instead of the normal + mouse event handling. + Setting the arrange mode to true will fail if lists selection mode is set. Selection + mode and arrange do not work together. + Setting the mode to true also fails in case there is no model set or the underlying + model does not support Qt::MoveAction. + Returns true if the mode setting was succesfull. +*/ +bool HbListView::setArrangeMode(const bool arrangeMode) +{ + Q_D(HbListView); + if (arrangeMode != d->mArrangeMode) { + if (arrangeMode == true) { + if (d->mSelectionMode != HbAbstractItemView::NoSelection + || !d->mModelIterator->model() + || !(d->mModelIterator->model()->supportedDropActions().testFlag(Qt::MoveAction))) { + return false; + } + if (d->mGestureFilter) { + removeSceneEventFilter(d->mGestureFilter); + d->mFilterRemoved = true; + } + verticalScrollBar()->setInteractive(true); + + } else { + if (d->mFilterRemoved) { + installSceneEventFilter(d->mGestureFilter); + d->mFilterRemoved = false; + } + verticalScrollBar()->setInteractive(false); + } + d->mArrangeMode = arrangeMode; + d->mAnimateItems = !d->mArrangeMode; + } + return true; +} + +/*! + \reimp +*/ +void HbListView::mousePressEvent(QGraphicsSceneMouseEvent *event) +{ + Q_D(HbListView); + if (d->mArrangeMode + && d->mSelectionMode == HbAbstractItemView::NoSelection + && !d->mDraggedItem) { + + if (d->mFilterRemoved == false && d->mGestureFilter) { + removeSceneEventFilter(d->mGestureFilter); + d->mFilterRemoved = true; + } + + d->mDraggedItem = d->itemAt(event->scenePos()); + if(d->mDraggedItem) { + d->mDraggedItemIndex = d->mDraggedItem->modelIndex(); + + if (d->mDraggedItemIndex.isValid()) { + setCurrentIndex(d->mDraggedItemIndex); + d->mMousePressTimer.restart(); + d->mMousePressPos = event->scenePos(); + d->mOriginalTransform = d->mDraggedItem->transform(); + d->mDraggedItem->setZValue(d->mDraggedItem->zValue() + 1); + d->mDraggedItem->setPressed(true); + + connect(this, SIGNAL(scrollPositionChanged(QPointF)), this, SLOT(scrolling(QPointF))); + Hb::InteractionModifiers modifiers = 0; + if (d->mWasScrolling) { + modifiers |= Hb::ModifierScrolling; + } + HbWidgetFeedback::triggered(d->mDraggedItem,Hb::InstantPressed,modifiers); + } else { + d->mDraggedItem = 0; + } + } + } else { + if (!d->mDraggedItem) { + HbAbstractItemView::mousePressEvent(event); + } + } +} + +/*! + \reimp +*/ +void HbListView::mouseMoveEvent(QGraphicsSceneMouseEvent *event) +{ + Q_D(HbListView); + + if (d->mArrangeMode + && d->mSelectionMode == HbAbstractItemView::NoSelection + && d->mDraggedItem) { + + if (!isScrolling()) { + // move the item with the cursor to indicate the move + d->mDraggedItem->translate(0, event->scenePos().y() - event->lastScenePos().y()); + + if (d->mMousePressTimer.elapsed() >= FLICK_TIMEOUT) { + d->moveDraggedItemTo(event->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; + d->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 = d->mContainer->items().first()->modelIndex(); + QModelIndex lastItemIndex = d->mContainer->items().last()->modelIndex(); + // If the item is dragged up in the list (and there are more items to show), scroll up + if (!isScrolling() + && !isVisible(firstItemIndex) + && event->scenePos().y() < d->mMousePressPos.y() + && event->pos().y() < itemByIndex(firstVisible)->size().height()) { + d->mScrollStartMousePos = event->scenePos(); + d->mLastScrollPos = QPointF(0,0); + d->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 (!isScrolling() + && !isVisible(lastItemIndex) + && event->scenePos().y() > d->mMousePressPos.y() + && event->pos().y() > (size().height() - itemByIndex(lastVisible)->size().height())) { + d->mScrollStartMousePos = event->scenePos(); + d->mLastScrollPos = QPointF(0,0); + d->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 (event->pos().y() < (size().height() - itemByIndex(lastVisible)->size().height()) + && event->pos().y() > itemByIndex(firstVisible)->size().height() + && isScrolling()) { + d->stopAnimating(); + } + } + } else { + HbAbstractItemView::mouseMoveEvent(event); + } +} + + +/*! + This slot is called when the arrangeMode is true, user is dragging an item + and the underlying scrollarea is moving. +*/void HbListView::scrolling(QPointF newPosition) +{ + Q_UNUSED(newPosition); + + Q_D(HbListView); + if (d->mArrangeMode + && d->mSelectionMode == HbAbstractItemView::NoSelection + && d->mDraggedItem) { + + QPointF itemPos = d->itemBoundingRect(d->mDraggedItem).topLeft(); + if (d->mLastScrollPos.isNull()) { + d->mLastScrollPos = itemPos; + } + + QPointF delta = d->mLastScrollPos - itemPos; + d->mLastScrollPos = itemPos + delta; + + d->moveDraggedItemTo(d->mScrollStartMousePos); + + if (d->mDraggedItem) { + d->mDraggedItem->translate(delta.x(), delta.y()); + } + } +} + + + +/*! + \reimp +*/ +void HbListView::mouseReleaseEvent(QGraphicsSceneMouseEvent *event) +{ + Q_D(HbListView); + + if (d->mArrangeMode + && d->mSelectionMode == HbAbstractItemView::NoSelection + && d->mDraggedItem) { + + disconnect(this, SIGNAL(scrollPositionChanged(QPointF)), this, SLOT(scrolling(QPointF))); + + if (isScrolling()) { + d->stopAnimating(); + } + + // remove item's drag indications + d->mDraggedItem->setOpacity(1.0); + d->mDraggedItem->setTransform(d->mOriginalTransform); + d->mDraggedItem->setZValue(d->mDraggedItem->zValue() - 1); + d->mDraggedItem->setPressed(false); + + if (d->itemAt(event->scenePos())) { + int downTime = d->mMousePressTimer.elapsed(); + // this seems to be a flick rather than item move, so start + // scrolling + qreal distance = event->scenePos().y() - d->mMousePressPos.y(); + if (downTime > 0 && downTime < FLICK_TIMEOUT + && qAbs(distance) > FLICKMINDISTANCE ) { + d->animateScroll(QPointF (0.0f, (distance * 1000 / downTime) * SCROLLSPEED_FACTOR)); + } + } + + Hb::InteractionModifiers modifiers = 0; + if (d->mWasScrolling) { + modifiers |= Hb::ModifierScrolling; + } + HbWidgetFeedback::triggered(d->mDraggedItem,Hb::InstantReleased,modifiers); + d->mDraggedItem = 0; + + } else { + HbAbstractItemView::mouseReleaseEvent(event); + } +} + +/*! + Moves the item in row \a from to row \a to. +*/ + void HbListView::move(const QModelIndex &from, const QModelIndex &to) +{ + Q_D(HbListView); + + int fromRow = from.row(); + int toRow = to.row(); + + if (from == to + || fromRow < 0 + || toRow < 0 + || fromRow >= model()->rowCount() + || toRow >= model()->rowCount()) { + return; + } + + QModelIndexList indexList; + indexList.append(from); + QMimeData* dragAndDropData = model()->mimeData(indexList); + + model()->removeRow(fromRow, d->mModelIterator->rootIndex()); + model()->dropMimeData(dragAndDropData, + Qt::MoveAction, + toRow, + 0, + d->mModelIterator->rootIndex()); +} + +/*! + \reimp +*/ +void HbListView::rowsInserted(const QModelIndex &parent, int start, int end) +{ + Q_D(HbListView); + + if (parent == d->mModelIterator->rootIndex()) { + HbAbstractItemView::rowsInserted(parent, start, end); + bool animate = d->mEnabledAnimations & HbAbstractItemView::Appear ? d->mAnimateItems : false; + if (!d->mArrangeMode && animate) { + d->startAppearEffect(parent, start, end); + } + } +} + +/*! + \reimp +*/ +void HbListView::rowsAboutToBeRemoved(const QModelIndex &parent, int start, int end) +{ + Q_D(HbListView); + + if (parent == d->mModelIterator->rootIndex()) { + for (int i = start; i <= end; i++) { + HbAbstractViewItem* item = viewItem(i); + if (item) { + if (d->mDraggedItem == item) { + d->mDraggedItem = 0; + } + bool animate = d->mEnabledAnimations & HbAbstractItemView::Disappear ? d->mAnimateItems : false; + if (!d->mArrangeMode && animate) { + d->mItemsAboutToBeDeleted.append(item); + } + } + } + } +} + +void HbListView::rowsRemoved(const QModelIndex &parent, int start, int end) +{ + Q_D(HbListView); + if (parent == d->mModelIterator->rootIndex()) { + bool animate = d->mEnabledAnimations & HbAbstractItemView::Disappear ? d->mAnimateItems: false; + if (animate) { + for (int i = 0; i < d->mItemsAboutToBeDeleted.count(); i++) { + HbEffect::start(d->mItemsAboutToBeDeleted.at(i), + "viewitem", + "disappear", + d->mContainer, + "animationFinished"); + } + d->mItemsAboutToBeDeleted.clear(); + } + HbAbstractItemView::rowsRemoved(parent, start, end); + } +} + +/*! + \reimp +*/ +void HbListView::dataChanged(const QModelIndex &topLeft, const QModelIndex &bottomRight) +{ + Q_D(const HbListView); + + QList items = d->mContainer->items(); + + if (!items.isEmpty() && + topLeft.parent() == d->mModelIterator->rootIndex() + && items.first()->modelIndex().row() <= bottomRight.row() + && topLeft.row() <= items.last()->modelIndex().row()) { + HbAbstractItemView::dataChanged(topLeft, bottomRight); + } +} + +/*! + \reimp + Handles change concerning multiline support of secondary texts at orientation switch +*/ +void HbListView::orientationChanged(Qt::Orientation newOrientation) +{ + Q_D(HbListView); + Q_UNUSED(newOrientation); + HbAbstractItemContainer *container = d->mContainer; + if (container) { + QList prototypes = container->itemPrototypes(); + int countPrototypes = prototypes.count(); + for (int i=0; i< countPrototypes; i++) { + HbListViewItem *prototypeItem = qobject_cast(prototypes.at(i)); + if ( prototypeItem + && prototypeItem->stretchingStyle() == HbListViewItem::StretchLandscape + && prototypeItem->graphicsSize() != HbListViewItem::Thumbnail) { + int minimum(0); + int maximum(0); + prototypeItem->getSecondaryTextRowCount(minimum, maximum); + if ( minimum != 1 + || maximum != 1) { + QList items = container->items(); + int countItems = items.count(); + for (int j=0; j< countItems; j++) { + HbAbstractViewItem *viewItem = items.at(j); + if ( viewItem + && viewItem->prototype() == prototypeItem) { + viewItem->updatePrimitives(); + } + } + } + } + } + } + HbAbstractItemView::orientationChanged(newOrientation); +} + + +#include "moc_hblistview.cpp"