/**************************************************************************** ** ** Copyright (C) 2009 Nokia Corporation and/or its subsidiary(-ies). ** All rights reserved. ** Contact: openBossa - INdT (renato.chencarek@openbossa.org) ** ** $QT_BEGIN_LICENSE:LGPL$ ** No Commercial Usage ** This file contains pre-release code and may not be distributed. ** You may use this file in accordance with the terms and conditions ** contained in the Technology Preview License Agreement accompanying ** this package. ** ** GNU Lesser General Public License Usage ** Alternatively, 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 ** the openBossa stream from INdT (renato.chencarek@openbossa.org). ** $QT_END_LICENSE$ ** ****************************************************************************/ #include #include #include #include "listview.h" #include "kineticscroll.h" class ListViewItemPrivate { public: ListViewItemPrivate(ListViewItem *qptr); ListViewItem *q; int index; ListView *view; QSizeF size; bool isPressed; QSizeF preferredSize; void setIndex(int value); void setPressed(bool selected); }; ListViewItemPrivate::ListViewItemPrivate(ListViewItem *qptr) : q(qptr), index(-1), view(0), isPressed(false), preferredSize(QSizeF(50, 50)) { } void ListViewItemPrivate::setIndex(int value) { // the index may stay the same when an item is removed and becomes // the previous one, but the renderer contents must be changed. //if (index != value) { q->indexChange(index, value); index = value; q->update(); //} } void ListViewItemPrivate::setPressed(bool pressed) { if (pressed != isPressed) { isPressed = pressed; q->selectionChanged(); } } ListViewItem::ListViewItem(QGraphicsItem *parent) : QGraphicsItem(parent), d(new ListViewItemPrivate(this)) { } ListViewItem::~ListViewItem() { delete d; } int ListViewItem::index() const { return d->index; } ListView *ListViewItem::listView() const { return d->view; } QSizeF ListViewItem::preferredSize() const { return d->preferredSize; } void ListViewItem::setPreferredSize(const QSizeF &size) { d->preferredSize = size; } bool ListViewItem::isPressed() const { return d->isPressed; } void ListViewItem::contentsChanged() { update(); } void ListViewItem::selectionChanged() { update(); } void ListViewItem::indexChange(int oldIndex, int newIndex) { Q_UNUSED(oldIndex); Q_UNUSED(newIndex); update(); } QSizeF ListViewItem::size() const { return d->size; } void ListViewItem::resize(const QSizeF &value) { if (value != d->size) { prepareGeometryChange(); d->size = value; update(); } } QRectF ListViewItem::boundingRect() const { return QRectF(QPointF(), size()); } class ListViewPrivate { public: ListViewPrivate(ListView *qptr); void init(); void refillRenderers(); void reconfigureRenderers(); void repositionRenderers(); bool setPixelPosition(int value); int visibleItemCount(); void setMaximumOffset(int value); void updateMaximumOffset(); int totalCount() const { return !model ? 0 : model->count(); } int itemSpace; int currentItem; int offset; int maximumOffset; int spareRenderers; AbstractModel *model; QList children; AbstractListViewCreator *creator; private: ListView *q; }; ListViewPrivate::ListViewPrivate(ListView *qptr) : itemSpace(50), currentItem(0), maximumOffset(0), spareRenderers(2), model(0), creator(0), q(qptr) { offset = -itemSpace; } void ListViewPrivate::init() { q->setHandlesChildEvents(true); q->setFlag(QGraphicsItem::ItemHasNoContents); q->setFlag(QGraphicsItem::ItemClipsChildrenToShape); } bool ListViewPrivate::setPixelPosition(int value) { value = qBound(0, value, q->maximumOffset()); if (value == q->offset()) return false; const int idx = value / itemSpace; const int overlap = idx - currentItem; if (overlap == 0) { offset = -itemSpace - (value % itemSpace); } else if (overlap == -1 || overlap == 1) { currentItem += overlap; offset = (currentItem - 1) * itemSpace - value; int newIdx; ListViewItem *renderer; if (overlap == 1) { renderer = children.takeFirst(); children.append(renderer); newIdx = currentItem + children.count() - spareRenderers; } else { renderer = children.takeLast(); children.prepend(renderer); newIdx = currentItem - 1; } if (newIdx >= 0 && newIdx < totalCount()) { renderer->d->setIndex(newIdx); renderer->show(); } else renderer->hide(); } else { currentItem += overlap; offset = (currentItem - 1) * itemSpace - value; refillRenderers(); } repositionRenderers(); return true; } void ListViewPrivate::reconfigureRenderers() { if (!model) return; const int w = q->size().width(); const int h = q->size().height(); int nitems = qRound(ceil(h / (qreal)itemSpace)); if (currentItem >= totalCount() || nitems >= totalCount()) { currentItem = 0; offset = -itemSpace; } nitems += spareRenderers; if (!children.isEmpty()) { while (nitems < children.count()) delete children.takeLast(); } foreach (ListViewItem *item, children) item->resize(QSizeF(w, itemSpace)); if (creator && nitems > children.count()) { while (nitems > children.count()) { ListViewItem *item = creator->create(); if (item) { children.append(item); item->d->view = q; item->setParentItem(q); item->resize(QSizeF(w, itemSpace)); } } } repositionRenderers(); refillRenderers(); } void ListViewPrivate::refillRenderers() { if (children.isEmpty()) return; if (totalCount() == 0) { foreach (ListViewItem *item, children) item->hide(); return; } ListViewItem *first = children[0]; if (currentItem > 0) { first->d->setIndex(currentItem - 1); first->show(); } else first->hide(); const int nc = children.count(); const int end = qMin(currentItem + nc - 1, totalCount()); for (int i = currentItem; i < end; i++) { ListViewItem *item = children[i - currentItem + 1]; item->d->setIndex(i); item->show(); } for (int i = end - currentItem + 1; i < nc; i++) children[i]->hide(); } void ListViewPrivate::repositionRenderers() { int x = 0; int y = offset; foreach (ListViewItem *item, children) { item->setPos(x, y); y += itemSpace; } } int ListViewPrivate::visibleItemCount() { int r = children.count(); int e = totalCount(); if (r == e) return e - 1; else if (r > e) return e; else return qMax(r - spareRenderers, 0); } void ListViewPrivate::setMaximumOffset(int value) { value = qMax(value, 0); if (maximumOffset != value) { maximumOffset = value; emit q->maximumOffsetChanged(); } } void ListViewPrivate::updateMaximumOffset() { if (totalCount() == 0) setMaximumOffset(0); else setMaximumOffset(qRound(totalCount() * itemSpace - q->size().height())); } ListView::ListView(QGraphicsItem *parent) : QGraphicsWidget(parent), d_ptr(new ListViewPrivate(this)) { d = d_ptr; d_ptr->init(); } ListView::ListView(ListViewPrivate &dptr, QGraphicsItem *parent) : QGraphicsWidget(parent), d_ptr(&dptr) { d = d_ptr; d_ptr->init(); } ListView::~ListView() { if (d->creator) delete d->creator; delete d; } AbstractModel *ListView::model() const { return d->model; } void ListView::setModel(AbstractModel *model) { if (d->model == model) return; if (d->model) { disconnect(d->model, SIGNAL(updated()), this, SLOT(reconfigureRenderers())); disconnect(d->model, SIGNAL(itemAdded(int)), this, SLOT(itemAdded(int))); disconnect(d->model, SIGNAL(itemChanged(int)), this, SLOT(itemChanged(int))); disconnect(d->model, SIGNAL(itemRemoved(int)), this, SLOT(itemRemoved(int))); disconnect(d->model, SIGNAL(itemMoved(int, int)), this, SLOT(itemMoved(int, int))); } d->model = model; if (d->model) { connect(d->model, SIGNAL(updated()), this, SLOT(reconfigureRenderers())); connect(d->model, SIGNAL(itemAdded(int)), this, SLOT(itemAdded(int))); connect(d->model, SIGNAL(itemChanged(int)), this, SLOT(itemChanged(int))); connect(d->model, SIGNAL(itemRemoved(int)), this, SLOT(itemRemoved(int))); connect(d->model, SIGNAL(itemMoved(int, int)), this, SLOT(itemMoved(int, int))); } modelChanged(); } void ListView::setCreator(AbstractListViewCreator *creator) { if (d->creator == creator) return; if (d->creator) delete d->creator; d->creator = creator; if (creator) { // get item preferred size just once ListViewItem *item = creator->create(); if (item) { d->itemSpace = qMax(item->preferredSize().height(), 1); delete item; d->updateMaximumOffset(); } } } int ListView::offset() const { return (d->currentItem - 1) * d->itemSpace - d->offset; } void ListView::setOffset(int position) { int oldOffset = offset(); d->setPixelPosition(qBound(0, position, maximumOffset())); if (oldOffset != offset()) emit offsetChanged(); } int ListView::maximumOffset() const { return d->maximumOffset; } void ListView::resizeEvent(QGraphicsSceneResizeEvent *event) { QGraphicsWidget::resizeEvent(event); d->updateMaximumOffset(); d->reconfigureRenderers(); } void ListView::reconfigureRenderers() { d->reconfigureRenderers(); } int ListView::indexAtOffset(int value) { if (value < 0 || value > size().height()) return -1; int idx = (value + offset()) / d->itemSpace; return (idx < d->totalCount()) ? idx : -1; } ListViewItem *ListView::itemFromIndex(int index) { if (index < 0 || index >= d->totalCount() || d->currentItem < 0) return 0; int base = d->currentItem; int top = base + d->visibleItemCount(); int cindex = index - base + 1; if (cindex < d->children.count() && base <= index && index <= top) return d->children[cindex]; else return 0; } void ListView::modelChanged() { d->currentItem = 0; d->updateMaximumOffset(); setOffset(0); reconfigureRenderers(); } void ListView::itemChanged(int index) { ListViewItem *item = itemFromIndex(index); if (item) item->contentsChanged(); } void ListView::itemAdded(int index) { Q_UNUSED(index); // XXX: optimize (check if it's in visible area) d->updateMaximumOffset(); reconfigureRenderers(); } void ListView::itemRemoved(int index) { Q_UNUSED(index); // XXX: optimize (check if it's in visible area) d->updateMaximumOffset(); reconfigureRenderers(); } void ListView::itemMoved(int oldIndex, int newIndex) { Q_UNUSED(oldIndex); Q_UNUSED(newIndex); // XXX: optimize (check if it's in visible area) reconfigureRenderers(); } void ListView::setItemPressed(ListViewItem *item, bool pressed) { item->d->setPressed(pressed); } class KineticListViewPrivate : public ListViewPrivate { public: KineticListViewPrivate(KineticListView *qptr); bool isClickPossible(int y); int getPosSmooth(int y); bool moveOffset(int offset); void showSelection(); void hideSelection(); bool dragging; bool mouseDown; int actualPosY; int mouseDownPos; int moveConstant; int clickConstant; double clickInitTime; double clickBlockTime; KineticScroll *kinetic; ListViewItem *lastSelectedRenderer; private: KineticListView *q; }; KineticListViewPrivate::KineticListViewPrivate(KineticListView *qptr) : ListViewPrivate(qptr), kinetic(0), q(qptr) { dragging = false; moveConstant = 15; clickConstant = 20; clickInitTime = 0.4; clickBlockTime = 0.5; actualPosY = -1; mouseDownPos = -1; lastSelectedRenderer = 0; } void KineticListViewPrivate::showSelection() { if (!isClickPossible(actualPosY)) return; int idx = q->indexAtOffset(actualPosY); if (idx >= 0) { if (lastSelectedRenderer) q->setItemPressed(lastSelectedRenderer, false); lastSelectedRenderer = q->itemFromIndex(idx); if (lastSelectedRenderer) q->setItemPressed(lastSelectedRenderer, true); } } void KineticListViewPrivate::hideSelection() { if (lastSelectedRenderer) q->setItemPressed(lastSelectedRenderer, false); lastSelectedRenderer = 0; } bool KineticListViewPrivate::isClickPossible(int y) { if (dragging || mouseDownPos < 0) return false; else return abs(y - mouseDownPos) <= clickConstant; } int KineticListViewPrivate::getPosSmooth(int y) { if (abs(mouseDownPos - y) <= moveConstant) return y; else if (mouseDownPos - y < 0) return y - moveConstant; else return y + moveConstant; } bool KineticListViewPrivate::moveOffset(int value) { int finalOffset = q->offset() - value; q->setOffset(finalOffset); if (value == 0 || finalOffset != q->offset()) { kinetic->kineticStop(); return false; } return true; } KineticListView::KineticListView(QGraphicsItem *parent) : ListView(*new KineticListViewPrivate(this), parent) { d = static_cast(d_ptr); d->kinetic = new KineticScroll(this); connect(d->kinetic, SIGNAL(signalMoveOffset(int)), SLOT(kineticMove(int))); } KineticListView::KineticListView(KineticListViewPrivate &dptr, QGraphicsItem *parent) : ListView(dptr, parent) { d = static_cast(d_ptr); d->kinetic = new KineticScroll(this); connect(d->kinetic, SIGNAL(signalMoveOffset(int)), SLOT(kineticMove(int))); } KineticListView::~KineticListView() { } void KineticListView::kineticMove(int value) { d->moveOffset(value); } void KineticListView::mousePressEvent(QGraphicsSceneMouseEvent *e) { int y = e->pos().y(); d->mouseDownPos = y; d->dragging = !d->kinetic->mouseDown(y); d->actualPosY = y; d->mouseDown = true; d->showSelection(); } void KineticListView::mouseReleaseEvent(QGraphicsSceneMouseEvent *e) { d->mouseDown = false; if (d->mouseDownPos >= 0) { int y = e->pos().y(); if (d->isClickPossible(y)) { int idx = indexAtOffset(y); if (idx >= 0 && idx < d->totalCount()) emit itemClicked(idx); d->kinetic->mouseCancel(); } else { d->kinetic->mouseUp(d->getPosSmooth(y)); } } d->hideSelection(); } void KineticListView::mouseMoveEvent(QGraphicsSceneMouseEvent *e) { if (d->mouseDownPos >= 0) { int y = e->pos().y(); d->actualPosY = y; if (!d->isClickPossible(y)) { d->dragging = true; d->hideSelection(); } if (abs(d->mouseDownPos - y) > d->moveConstant) d->kinetic->mouseMove(d->getPosSmooth(y)); } } void KineticListView::modelChanged() { d->kinetic->kineticStop(); ListView::modelChanged(); }