summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorAdriano Rezende <adriano.rezende@openbossa.org>2009-11-16 11:20:11 -0300
committerAdriano Rezende <adriano.rezende@openbossa.org>2009-11-16 11:28:16 -0300
commitbd197df9d2a5a4f9f858911f3c1d7af13beb2bb0 (patch)
treee498b35c62db86ff370daba493e4b7a6fdb0b6d2
parent4d5b88f2cd3d1f6a433d4d0bce5287bf646793c6 (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.cpp719
-rw-r--r--shared/listview.h169
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