diff options
author | Adriano Rezende <adriano.rezende@openbossa.org> | 2009-11-16 11:20:11 -0300 |
---|---|---|
committer | Adriano Rezende <adriano.rezende@openbossa.org> | 2009-11-16 11:28:16 -0300 |
commit | bd197df9d2a5a4f9f858911f3c1d7af13beb2bb0 (patch) | |
tree | e498b35c62db86ff370daba493e4b7a6fdb0b6d2 | |
parent | 4d5b88f2cd3d1f6a433d4d0bce5287bf646793c6 (diff) |
Shared: Added ListView and KineticListView widgets
The ListView and KineticListView implements a scalable list.
Signed-off-by: Adriano Rezende <adriano.rezende@openbossa.org>
-rw-r--r-- | shared/listview.cpp | 719 | ||||
-rw-r--r-- | shared/listview.h | 169 |
2 files changed, 888 insertions, 0 deletions
diff --git a/shared/listview.cpp b/shared/listview.cpp new file mode 100644 index 0000000..feac60d --- /dev/null +++ b/shared/listview.cpp @@ -0,0 +1,719 @@ +/**************************************************************************** +** +** 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 <math.h> + +#include <QPainter> +#include <QGraphicsSceneMouseEvent> + +#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(); + + int totalCount() const { return !model ? 0 : model->count(); } + + int itemSpace; + int currentItem; + + int offset; + int spareRenderers; + + AbstractModel *model; + QList<ListViewItem *> children; + AbstractListViewCreator *creator; + +private: + ListView *q; +}; + + +ListViewPrivate::ListViewPrivate(ListView *qptr) + : itemSpace(50), + currentItem(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<int>(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); +} + + + +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 = item->preferredSize().height(); + delete item; + } + } +} + +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())); + + // XXX: must emit also when maximum offset has changed + if (oldOffset != offset()) + emit offsetChanged(); +} + +int ListView::maximumOffset() const +{ + if (d->totalCount() == 0) + return 0; + + const int total = d->totalCount() * d->itemSpace; + const int space = size().height(); + + return qMax(qRound(total - space), 0); +} + +void ListView::resizeEvent(QGraphicsSceneResizeEvent *event) +{ + QGraphicsWidget::resizeEvent(event); + 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; + 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) + reconfigureRenderers(); +} + +void ListView::itemRemoved(int index) +{ + Q_UNUSED(index); + // XXX: optimize (check if it's in visible area) + 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<KineticListViewPrivate *>(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<KineticListViewPrivate *>(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(); +} diff --git a/shared/listview.h b/shared/listview.h new file mode 100644 index 0000000..594f866 --- /dev/null +++ b/shared/listview.h @@ -0,0 +1,169 @@ +/**************************************************************************** +** +** 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$ +** +****************************************************************************/ + +#ifndef LISTVIEW_H +#define LISTVIEW_H + +#include <QPointF> +#include <QGraphicsWidget> + +#include "model.h" + +class ListView; +class ListViewItem; +class ListViewPrivate; +class ListViewItemPrivate; + + +class AbstractListViewCreator +{ +public: + virtual ListViewItem *create() = 0; +}; + + +template <typename T> +class ListViewCreator : public AbstractListViewCreator +{ +public: + ListViewItem *create() { return new T(); } +}; + + +class ListViewItem : public QGraphicsItem +{ +public: + ListViewItem(QGraphicsItem *parent = 0); + ~ListViewItem(); + + int index() const; + bool isPressed() const; + + QSizeF size() const; + QRectF boundingRect() const; + + QSizeF preferredSize() const; + void setPreferredSize(const QSizeF &size); + +protected: + ListView *listView() const; + void resize(const QSizeF &value); + + virtual void contentsChanged(); + virtual void selectionChanged(); + virtual void indexChange(int oldIndex, int newIndex); + +private: + ListViewItemPrivate *d; + + friend class ListView; + friend class ListViewPrivate; + friend class ListViewItemPrivate; +}; + + +class ListView : public QGraphicsWidget +{ + Q_OBJECT + +public: + ListView(QGraphicsItem *parent = 0); + ~ListView(); + + AbstractModel *model() const; + void setModel(AbstractModel *model); + + int offset() const; + int maximumOffset() const; + + void setCreator(AbstractListViewCreator *creator); + + int indexAtOffset(int offset); + ListViewItem *itemFromIndex(int index); + +signals: + void offsetChanged(); + void itemClicked(int index); + +public slots: + void setOffset(int position); + +protected: + ListViewPrivate *d_ptr; + + ListView(ListViewPrivate &dptr, QGraphicsItem *parent = 0); + + virtual void modelChanged(); + void resizeEvent(QGraphicsSceneResizeEvent *event); + void setItemPressed(ListViewItem *item, bool pressed); + +protected slots: + void reconfigureRenderers(); + + virtual void itemAdded(int index); + virtual void itemChanged(int index); + virtual void itemRemoved(int index); + virtual void itemMoved(int oldIndex, int newIndex); + +private: + ListViewPrivate *d; + friend class ListViewPrivate; +}; + + + +class KineticListViewPrivate; + +class KineticListView : public ListView +{ + Q_OBJECT + +public: + KineticListView(QGraphicsItem *parent = 0); + ~KineticListView(); + +public Q_SLOTS: + void kineticMove(int); + +protected: + KineticListView(KineticListViewPrivate &dptr, QGraphicsItem *parent = 0); + + void modelChanged(); + void mousePressEvent(QGraphicsSceneMouseEvent *event); + void mouseMoveEvent(QGraphicsSceneMouseEvent *event); + void mouseReleaseEvent(QGraphicsSceneMouseEvent *event); + +private: + KineticListViewPrivate *d; + friend class KineticListViewPrivate; +}; + +#endif |