summaryrefslogtreecommitdiffstats
path: root/weather
diff options
context:
space:
mode:
authorLuiz Agostini <luiz.agostini@openbossa.org>2009-11-03 21:10:20 -0300
committerLuiz Agostini <luiz.agostini@openbossa.org>2009-11-05 00:42:09 -0300
commit122ea22c9cb15da3aed92cd720610d69af80ac7c (patch)
treebccd03ad5dd9efc28553c694f3bc82af207af656 /weather
parentc88a51e51bbb5fa318456826aa63a5fba428f6b3 (diff)
Weather: city list.
Signed-off-by: Luiz Agostini <luiz.agostini@openbossa.org>
Diffstat (limited to 'weather')
-rw-r--r--weather/bootmanager.cpp2
-rw-r--r--weather/citylist.cpp398
-rw-r--r--weather/citylist.h143
-rw-r--r--weather/contentlist.cpp439
-rw-r--r--weather/contentlist.h262
-rw-r--r--weather/weather.pro8
6 files changed, 1250 insertions, 2 deletions
diff --git a/weather/bootmanager.cpp b/weather/bootmanager.cpp
index dbe2da6..08a2efe 100644
--- a/weather/bootmanager.cpp
+++ b/weather/bootmanager.cpp
@@ -10,6 +10,7 @@
#include "titlebar.h"
#include "citycarroussel.h"
#include "scrollbar.h"
+#include "citylist.h"
#include <QDebug>
@@ -36,6 +37,7 @@ void BootManager::run()
count += TitleBar::loadImages();
count += CityCarroussel::loadImages();
count += ScrollBar::loadImages();
+ count += CityList::loadImages();
m_imagesLoaded = count == 0;
if (m_imagesLoaded)
diff --git a/weather/citylist.cpp b/weather/citylist.cpp
new file mode 100644
index 0000000..f0377d4
--- /dev/null
+++ b/weather/citylist.cpp
@@ -0,0 +1,398 @@
+#include "citylist.h"
+#include "settings.h"
+#include "pixmaploader.h"
+#include <QPainter>
+#include <cmath>
+#include <QParallelAnimationGroup>
+#include <QPropertyAnimation>
+#include <QDebug>
+
+#define LIST_TOP_PIXMAP (PixmapLoader::getPic("list_top"))
+
+#define ITEM_BACKGROUND (PixmapLoader::getPic("list_item_bg"))
+#define SELECTED_ITEM_BACKGROUND (PixmapLoader::getPic("list_item_selected_bg"))
+
+#define ITEM_BUTTON_PIXMAP (PixmapLoader::getPic("button_list_delete"))
+#define ITEM_BUTTON_LEFT (Settings::scaleWidth(406.0))
+
+#define ITEM_CHECK_PIXMAP (PixmapLoader::getPic("list_check"))
+#define ITEM_CHECK_LEFT (Settings::scaleWidth(10.0))
+
+#define TEXT_LEFT (2 * ITEM_CHECK_LEFT + ITEM_CHECK_PIXMAP.width())
+
+#define CITY_NAME_FONT_SIZE (Settings::scaleHeight(50.0))
+
+static const int maxVisibleItems = 8;
+
+static inline qreal getCenterVerticalPos(QGraphicsItem *parent, QGraphicsItem *item)
+{
+ const qreal top = (parent->boundingRect().height() - item->boundingRect().height()) / 2;
+ return top - parent->boundingRect().top() - item->boundingRect().top();
+}
+
+static inline qreal getCenterVerticalPos(QGraphicsItem *item)
+{
+ return getCenterVerticalPos(item->parentItem(), item);
+}
+
+// CityListScrollBox
+
+#ifdef Q_OS_SYMBIAN
+static const int acceleration_factor = 12;
+#else
+static const int acceleration_factor = 64;
+#endif
+
+
+static const int max_gesture_time_ms = 500;
+static const qreal degrees = 30.0;
+static const qreal limit_angle_tg = tan(degrees * 3.141592 / 180.0);
+static const qreal min_gesture_length = 100.0;
+
+CityListScrollBox::CityListScrollBox(QGraphicsItem *content, QGraphicsItem *parent)
+ : GestureBox(parent)
+ , m_content(content)
+ , m_scroll(new ScrollBar(this))
+{
+ setFlag(QGraphicsItem::ItemClipsChildrenToShape, true);
+ if (m_content)
+ m_content->setParentItem(this);
+
+ m_scroll->setPos(Settings::scaleWidth(460.0), Settings::scaleHeight(32.5));
+}
+
+void CityListScrollBox::gestureMousePress(QPointF pos, bool &startGesture, bool &acceptClick)
+{
+ Q_UNUSED(pos);
+ startGesture = true;
+ acceptClick = !m_ticker.isActive();
+ if (m_ticker.isActive()) {
+ m_ticker.stop();
+ m_speed = 0.0;
+ }
+}
+
+void CityListScrollBox::gestureStart(QPointF pos)
+{
+ m_startPoint = pos;
+}
+
+void CityListScrollBox::contentPositionUpdated()
+{
+ qreal height = boundingRect().height();
+ qreal content = m_content->boundingRect().height();
+
+ if (height < content) {
+ qreal pos = content - height;
+ pos = -m_content->pos().y() / pos;
+ m_scroll->setValue(pos < 0.0 ? 0.0 : pos);
+ } else
+ if (m_content->pos().y() != 0.0) {
+ m_content->setPos(0.0, 0.0);
+ m_scroll->setValue(0.0);
+ }
+
+}
+
+bool CityListScrollBox::move(QPointF movement)
+{
+ if (m_content && m_content->boundingRect().height() > boundingRect().height()) {
+
+ qreal dist = movement.y();
+
+
+ if (dist > 0 && m_content->pos().y() < 0) {
+ qreal top = m_content->pos().y() + dist;
+ m_content->setPos(m_content->pos().x(), top > 0 ? 0 : top);
+ contentPositionUpdated();
+ return true;
+ }
+
+ if (dist < 0) {
+ qreal top = m_content->pos().y() + dist;
+ if (top + m_content->boundingRect().height() >= boundingRect().height()) {
+ m_content->setPos(m_content->pos().x(), top);
+ contentPositionUpdated();
+ return true;
+ }
+ }
+
+ }
+ return false;
+}
+
+void CityListScrollBox::gestureMove(QPointF pos, QPointF movement, QPointF speed)
+{
+ Q_UNUSED(pos);
+ Q_UNUSED(speed);
+ move(movement);
+}
+
+void CityListScrollBox::gestureEnd(QPointF pos, QPointF speed)
+{
+ qreal x = pos.x() - m_startPoint.x();
+ qreal y = pos.y() - m_startPoint.y();
+ if (fabs(x / y) > limit_angle_tg || fabs(y) < min_gesture_length)
+ return;
+
+ qreal topSpeed = Settings::scaleHeight(1500.0);
+ m_acceleration = topSpeed / acceleration_factor;
+ m_speed = qBound(-topSpeed, speed.y(), topSpeed);
+ if (m_speed != 0) {
+ m_time = QTime::currentTime();
+ m_ticker.start(20, this);
+ }
+}
+
+void CityListScrollBox::timerEvent(QTimerEvent *event)
+{
+ bool stopTimer = true;
+ if (m_speed != 0.0) {
+ QTime now(QTime::currentTime());
+ qreal movement = m_speed * (qreal)m_time.msecsTo(now) / 1000.0;
+ m_time = now;
+ stopTimer = !move(QPointF(0, movement));
+ if (!stopTimer) {
+ m_speed = m_speed > 0.0 ? qMax(qreal(0.0), m_speed - m_acceleration)
+ : qMin(qreal(0.0), m_speed + m_acceleration);
+ stopTimer = m_speed == 0.0;
+ }
+ }
+ if (stopTimer)
+ m_ticker.stop();
+ QObject::timerEvent(event);
+}
+
+// CityListItem
+
+CityListItem::CityListItem(const ForecastData &data, CityContentList *list)
+ : ContentListItem(0)
+ , m_height(ITEM_BACKGROUND.height())
+ , m_data(data)
+ , m_list(list)
+ , m_background(new QGraphicsPixmapItem(ITEM_BACKGROUND, this))
+ , m_check(new QGraphicsPixmapItem(ITEM_CHECK_PIXMAP, m_background))
+ , m_delete(new PixmapButton(80.0, ITEM_BUTTON_PIXMAP, m_background))
+ , m_text(createTextItem())
+{
+ m_delete->setPos(ITEM_BUTTON_LEFT, getCenterVerticalPos(m_delete));
+ m_check->setPos(ITEM_CHECK_LEFT, getCenterVerticalPos(m_check));
+ m_text->setPos(TEXT_LEFT, Settings::scaleHeight(5.0));
+ m_text->setCacheMode(ItemCoordinateCache);
+
+ connect(m_delete, SIGNAL(clicked()), this, SLOT(removeFromList()));
+
+ hide();
+}
+
+void CityListItem::removeFromList()
+{
+ m_list->removeItem(this);
+}
+
+
+QGraphicsSimpleTextItem *CityListItem::createTextItem()
+{
+ QGraphicsSimpleTextItem *result = new QGraphicsSimpleTextItem(this);
+
+ QFont font;
+ font.setFamily("Nokia Sans");
+ font.setPixelSize(CITY_NAME_FONT_SIZE);
+ font.setStyleStrategy(QFont::PreferAntialias);
+
+ QBrush brush(Qt::white);
+
+ QPen pen;
+
+ pen.setColor(Qt::white);
+ pen.setBrush(Qt::white);
+ pen.setJoinStyle(Qt::RoundJoin);
+
+ result->setPen(pen);
+ result->setBrush(brush);
+ result->setFont(font);
+
+ QFontMetrics m(font);
+
+ int len = ITEM_BUTTON_LEFT - TEXT_LEFT;
+
+ result->setText(m.elidedText(m_data.cityName(), Qt::ElideRight, len));
+
+ return result;
+
+}
+
+qreal CityListItem::contentHeight() const
+{
+ return m_height;
+}
+
+QAbstractAnimation *CityListItem::getShowAnimation()
+{
+ return getFadeAnimation(false, 150);
+}
+
+QAbstractAnimation *CityListItem::getHideAnimation()
+{
+ return getFadeAnimation(true, 150);
+}
+
+QAbstractAnimation *CityListItem::getFadeAnimation(bool hide, int msecs)
+{
+
+ setOpacity(hide ? 1.0 : 0.0);
+ show();
+
+ QPropertyAnimation* lResult = new QPropertyAnimation(this, "opacity");
+ lResult->setEasingCurve(QEasingCurve::OutExpo);
+ lResult->setStartValue(hide ? 1.0 : 0.0);
+ lResult->setEndValue(hide ? 0.0 : 1.0);
+ lResult->setDuration(msecs);
+ if (hide)
+ connect(lResult, SIGNAL(finished()), this, SLOT(doHide()));
+ return lResult;
+}
+
+// CityContentList
+
+CityContentList::CityContentList(QObject *holder)
+ : ContentList(0)
+ , m_holder(holder)
+{
+
+}
+
+QAbstractAnimation *CityContentList::getInsertAnimation(int idx, qreal height)
+{
+ if (idx < 0 || idx > itemCount())
+ return 0;
+
+ QList<QAbstractAnimation*> list;
+
+ if (m_holder && itemCount() < maxVisibleItems)
+ list.append(getMoveAnimation(m_holder, -height));
+
+ if (idx < itemCount()) {
+ for (int i = idx; i < itemCount(); ++i)
+ list.append(getItemMoveAnimation(i, height));
+ }
+
+ return createCompoundAnimation(list);
+}
+
+QAbstractAnimation *CityContentList::getRemoveAnimation(int idx)
+{
+ if (idx < 0 || idx >= itemCount())
+ return 0;
+
+ qreal offset = -getItem(idx)->contentHeight();
+ QList<QAbstractAnimation*> list;
+
+ if (m_holder && itemCount() <= maxVisibleItems)
+ list.append(getMoveAnimation(m_holder, -offset));
+
+ for (int i = idx + 1; i < itemCount(); ++i)
+ list.append(getItemMoveAnimation(i, offset));
+
+ return createCompoundAnimation(list);
+}
+
+QAbstractAnimation *CityContentList::createCompoundAnimation(QList<QAbstractAnimation*> list)
+{
+ if (list.count() == 0)
+ return 0;
+ if (list.count() == 1)
+ return list[0];
+ QParallelAnimationGroup *result = new QParallelAnimationGroup();
+ for (int i = 0; i < list.count(); ++i)
+ result->addAnimation(list[i]);
+ return result;
+
+}
+
+QAbstractAnimation *CityContentList::getMoveAnimation(QObject *object, qreal offset)
+{
+ if (!object)
+ return 0;
+ qreal itemTop = object->property(ITEM_TOP_PROPERTY_NAME).toReal();
+ QPropertyAnimation* lResult = new QPropertyAnimation(object, ITEM_TOP_PROPERTY_NAME);
+
+ lResult->setEasingCurve(QEasingCurve::OutExpo);
+ lResult->setStartValue(itemTop);
+ lResult->setEndValue(itemTop + offset);
+ lResult->setDuration(150);
+ return lResult;
+}
+
+QAbstractAnimation *CityContentList::getItemMoveAnimation(int idx, qreal offset)
+{
+ return getMoveAnimation(getItem(idx), offset);
+}
+
+// CityList
+
+CityList::CityList(QGraphicsItem *parent)
+ : QGraphicsItem(parent)
+ , m_itemHeight(ITEM_BACKGROUND.height())
+ , m_topHeight(LIST_TOP_PIXMAP.height())
+ , m_boundingRect(getBoundingRect())
+ , m_paintRect(getPaintRect())
+ , m_top(new QGraphicsPixmapItem(LIST_TOP_PIXMAP, this))
+ , m_list(new CityContentList(this))
+ , m_scrollBox(new CityListScrollBox(m_list, this))
+{
+ m_top->setPos(-m_top->boundingRect().left(), -m_top->boundingRect().top());
+ m_scrollBox->setRect(m_paintRect);
+}
+
+qreal CityList::initialTop() const
+{
+ int count = maxVisibleItems - m_list->itemCount();
+ if (count <= 0)
+ return 0;
+ return count * m_itemHeight;
+}
+
+QRectF CityList::getBoundingRect()
+{
+ QRectF result(QPointF(0.0, 0.0), Settings::windowSize());
+ result.setHeight(m_topHeight + maxVisibleItems * m_itemHeight);
+ result.moveTo(0.0, 0.0);
+ return result;
+}
+
+QRectF CityList::getPaintRect()
+{
+ QRectF result(QPointF(0.0, 0.0), Settings::windowSize());
+ result.setHeight(maxVisibleItems * m_itemHeight);
+ result.moveTo(0.0, m_topHeight);
+ return result;
+}
+
+int CityList::loadImages()
+{
+ PixmapLoader::load("list_top");
+ PixmapLoader::load("list_item_bg");
+ PixmapLoader::load("list_item_selected_bg");
+ PixmapLoader::load("button_list_delete");
+ PixmapLoader::load("list_check");
+ return 5;
+}
+
+void CityList::mousePressEvent(QGraphicsSceneMouseEvent *event)
+{
+ Q_UNUSED(event);
+}
+
+QRectF CityList::boundingRect () const
+{
+ return m_boundingRect;
+}
+
+void CityList::paint(QPainter *painter,
+ const QStyleOptionGraphicsItem *opt, QWidget *widget)
+{
+ Q_UNUSED(opt);
+ Q_UNUSED(widget);
+ painter->fillRect(m_paintRect, QColor(7, 18, 23, 255));
+}
diff --git a/weather/citylist.h b/weather/citylist.h
new file mode 100644
index 0000000..6e9a54f
--- /dev/null
+++ b/weather/citylist.h
@@ -0,0 +1,143 @@
+#ifndef CITYLIST_H
+#define CITYLIST_H
+
+#include <QBasicTimer>
+#include <QTime>
+#include <QGraphicsItem>
+
+#include "gesturebox.h"
+#include "contentlist.h"
+#include "forecastdata.h"
+#include "pixmapbutton.h"
+#include "scrollbar.h"
+
+// CityListScrollBox
+
+class CityListScrollBox : public QObject, public GestureBox
+{
+ Q_OBJECT
+public:
+ CityListScrollBox(QGraphicsItem *content, QGraphicsItem *parent = 0);
+
+signals:
+ void updated(qreal, qreal, qreal);
+
+protected:
+ void gestureMousePress(QPointF pos, bool &startGesture, bool &acceptClick);
+ void gestureStart(QPointF pos);
+ void gestureMove(QPointF pos, QPointF movement, QPointF speed);
+ void gestureEnd(QPointF pos, QPointF speed);
+
+ void timerEvent(QTimerEvent *event);
+
+private:
+ QGraphicsItem * const m_content;
+ QBasicTimer m_ticker;
+ qreal m_speed;
+ qreal m_acceleration;
+ QTime m_time;
+ QPointF m_startPoint;
+ ScrollBar *m_scroll;
+ bool move(QPointF movement);
+ void contentPositionUpdated();
+};
+
+// CityListItem
+
+class CityContentList;
+
+class CityListItem : public ContentListItem
+{
+ Q_OBJECT
+ Q_PROPERTY(qreal opacity READ opacity WRITE setOpacity);
+public:
+ CityListItem(const ForecastData &data, CityContentList *list);
+
+ qreal contentHeight() const;
+ QAbstractAnimation *getShowAnimation();
+ QAbstractAnimation *getHideAnimation();
+
+private slots:
+ void removeFromList();
+
+private:
+ const qreal m_height;
+ const ForecastData m_data;
+ CityContentList * const m_list;
+
+ QGraphicsPixmapItem * const m_background;
+ QGraphicsPixmapItem * const m_check;
+ PixmapButton * const m_delete;
+ QGraphicsSimpleTextItem * const m_text;
+
+ QAbstractAnimation *getFadeAnimation(bool hide, int msecs);
+
+
+ QGraphicsSimpleTextItem *createTextItem();
+
+private slots:
+ void doHide() { hide(); }
+
+};
+
+// CityContentList
+
+class CityContentList : public ContentList
+{
+public:
+ CityContentList(QObject *holder);
+
+ void addForecast(const ForecastData &data) { addItem(new CityListItem(data, this)); }
+
+protected:
+ QAbstractAnimation *getInsertAnimation(int idx, qreal height);
+ QAbstractAnimation *getRemoveAnimation(int idx);
+
+private:
+ QObject * const m_holder;
+ QAbstractAnimation *createCompoundAnimation(QList<QAbstractAnimation*> list);
+ QAbstractAnimation *getItemMoveAnimation(int idx, qreal offset);
+ QAbstractAnimation *getMoveAnimation(QObject *object, qreal offset);
+};
+
+// CityList
+
+class CityList : public QObject, public QGraphicsItem
+{
+ Q_OBJECT
+ Q_PROPERTY(qreal top READ getTop WRITE setTop);
+ Q_INTERFACES(QGraphicsItem);
+public:
+ CityList(QGraphicsItem *parent = 0);
+
+ QRectF boundingRect () const;
+ void paint(QPainter *painter, const QStyleOptionGraphicsItem *option, QWidget *widget = 0);
+
+ static int loadImages();
+
+ qreal initialTop() const;
+
+public slots:
+ void addForecast(const ForecastData &data) { m_list->addForecast(data); }
+
+protected:
+ void mousePressEvent(QGraphicsSceneMouseEvent *event);
+
+private:
+ const qreal m_itemHeight;
+ const qreal m_topHeight;
+ const QRectF m_boundingRect;
+ const QRectF m_paintRect;
+
+ inline QRectF getBoundingRect();
+ inline QRectF getPaintRect();
+
+ QGraphicsPixmapItem * const m_top;
+ CityContentList * const m_list;
+ CityListScrollBox * const m_scrollBox;
+
+ qreal getTop() { return pos().y(); }
+ void setTop(qreal top) { setPos(pos().x(), top); }
+};
+
+#endif // CITYLIST_H
diff --git a/weather/contentlist.cpp b/weather/contentlist.cpp
new file mode 100644
index 0000000..378fc5e
--- /dev/null
+++ b/weather/contentlist.cpp
@@ -0,0 +1,439 @@
+/****************************************************************************
+**
+** 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 "contentlist.h"
+#include <QPointer>
+
+// ContentListItem
+
+ContentListItem::ContentListItem(QGraphicsItem *parent)
+ : QGraphicsItem(parent)
+ , m_geometry(QRectF(0.0, 0.0, 0.0, 0.0))
+{
+ setFlag(QGraphicsItem::ItemHasNoContents, true);
+ setFlag(QGraphicsItem::ItemSendsGeometryChanges, true);
+ updateGeometry();
+}
+
+QRectF ContentListItem::boundingRect() const
+{
+ return m_geometry;
+}
+
+void ContentListItem::updateGeometry()
+{
+ if (!parentItem())
+ return;
+ QRectF geometry(0.0, 0.0, parentItem()->boundingRect().width() - pos().x(), contentHeight());
+ if (m_geometry != geometry) {
+ prepareGeometryChange();
+ m_geometry = geometry;
+ update();
+ }
+}
+
+QVariant ContentListItem::itemChange(GraphicsItemChange change, const QVariant &value)
+{
+ Q_UNUSED(value);
+ switch (change) {
+ case QGraphicsItem::ItemParentHasChanged:
+ case QGraphicsItem::ItemPositionHasChanged:
+ updateGeometry();
+ break;
+ default:
+ break;
+ }
+ return value;
+}
+
+void ContentListItem::paint(QPainter *painter,
+ const QStyleOptionGraphicsItem *option, QWidget *widget)
+{
+ Q_UNUSED(painter);
+ Q_UNUSED(option);
+ Q_UNUSED(widget);
+}
+
+// ContentListActivity
+
+void ContentListActivity::addActivity(ContentListActivity *activity)
+{
+ int idx = m_list.m_queue.indexOf(this) + 1;
+ if (idx > 0)
+ m_list.m_queue.insert(idx + m_insertPos++, activity);
+}
+
+bool ContentListActivity::active()
+{
+ return false;
+}
+
+// SignalActivity
+
+
+bool SignalActivity::run()
+{
+ emit notify();
+ return false;
+}
+
+// SortActivity
+
+bool SortActivity::run()
+{
+ qSort(m_list.m_items.begin(), m_list.m_items.end(), m_compare);
+ m_list.updateItems();
+ return false;
+}
+
+// AppendItemsActivity
+
+bool AppendItemsActivity::run()
+{
+ m_list.prepareGeometryChange();
+ m_list.doAppendItems(m_items, true);
+ m_list.update();
+ return false;
+}
+
+// AnimationActivity
+
+AnimationActivity::AnimationActivity(QAbstractAnimation *animation, ContentList &list)
+ : ContentListActivity(list)
+ , m_animation(animation)
+{
+}
+
+AnimationActivity::~AnimationActivity()
+{
+ if (m_animation && m_animation->state() == QAbstractAnimation::Stopped)
+ m_animation->deleteLater();
+}
+
+
+bool AnimationActivity::run()
+{
+ if (!m_animation || m_animation->state() != QAbstractAnimation::Stopped)
+ return false;
+
+ connect(m_animation, SIGNAL(finished()), this, SLOT(animationEnd()));
+ m_animation->start(QAbstractAnimation::DeleteWhenStopped);
+ return true;
+}
+
+void AnimationActivity::animationEnd()
+{
+ m_animation = 0;
+ activityEnd();
+}
+
+// RemoveActivity
+
+RemoveActivity::RemoveActivity(int idx, bool destroyItem, bool notify, ContentList &list)
+ : ContentListActivity(list)
+ , m_idx(idx)
+ , m_destroyItem(destroyItem)
+ , m_item(0)
+ , m_active(false)
+ , m_notify(notify)
+{
+}
+
+bool RemoveActivity::run()
+{
+ if (m_active)
+ return false;
+
+ m_item = m_list.getItem(m_idx);
+ if (!m_item)
+ return false;
+
+ QAbstractAnimation *animation = m_item->getHideAnimation();
+ if (animation) {
+ connect(animation, SIGNAL(finished()), this, SLOT(hideEnd()));
+ animation->start(QAbstractAnimation::DeleteWhenStopped);
+ m_active = true;
+ } else
+ hideEnd();
+
+ return m_active;
+}
+
+void RemoveActivity::hideEnd()
+{
+ m_item->hide();
+ QAbstractAnimation *animation = m_list.getRemoveAnimation(m_idx);
+ m_list.doRemoveItem(m_idx, m_notify);
+ if (m_destroyItem)
+ m_item->deleteLater();
+ m_active = animation != 0;
+ if (animation) {
+ connect(animation, SIGNAL(finished()), this, SLOT(activityEnd()));
+ animation->start(QAbstractAnimation::DeleteWhenStopped);
+ } else
+ activityEnd();
+}
+
+// InsertActivity
+
+InsertActivity::InsertActivity(int idx, ContentListItem* item, bool notify, ContentList &list)
+ : ContentListActivity(list)
+ , m_idx(idx)
+ , m_item(item)
+ , m_active(false)
+ , m_notify(notify)
+{
+}
+
+bool InsertActivity::run()
+{
+ if (m_active || !m_item)
+ return false;
+
+ m_idx = m_idx < 0 ? 0 : m_idx > m_list.itemCount() ? m_list.itemCount() : m_idx;
+
+
+ QAbstractAnimation *animation = m_list.getInsertAnimation(m_idx, m_item->contentHeight());
+ if (animation) {
+ connect(animation, SIGNAL(finished()), this, SLOT(showItem()));
+ animation->start(QAbstractAnimation::DeleteWhenStopped);
+ m_active = true;
+ } else
+ showItem();
+
+ return m_active;
+}
+
+void InsertActivity::showItem()
+{
+ m_list.doInsertItem(m_idx, m_item, m_notify);
+ m_item->show();
+ QAbstractAnimation *animation = m_item->getShowAnimation();
+ m_active = animation != 0;
+ if (animation) {
+ connect(animation, SIGNAL(finished()), this, SLOT(activityEnd()));
+ animation->start(QAbstractAnimation::DeleteWhenStopped);
+ } else
+ activityEnd();
+}
+
+// MoveActivity
+
+MoveActivity::MoveActivity(int from, int to, ContentList &list)
+ : ContentListActivity(list)
+ , m_from(from)
+ , m_to(to)
+{
+}
+
+bool MoveActivity::run()
+{
+ if (m_from < 0 || m_from >= m_list.itemCount())
+ return false;
+
+ m_to = m_to < 0 ? 0 : m_to > m_list.itemCount() ? m_list.itemCount() : m_to;
+
+ if (m_from == m_to || (m_from == m_list.itemCount() - 1 && m_to > m_from))
+ return false;
+
+ addActivity(new RemoveActivity(m_from, false, false, m_list));
+ addActivity(new InsertActivity(m_to, m_list.getItem(m_from), false, m_list));
+
+ return false;
+}
+
+// ContentList
+
+ContentList::ContentList(QGraphicsItem *parent)
+ : QGraphicsItem(parent)
+ , m_boundingRect(0.0, 0.0, 0.0, 0.0)
+{
+ setFlag(QGraphicsItem::ItemHasNoContents, true);
+}
+
+ContentList::ContentList(QList<ContentListItem*> items, QGraphicsItem *parent)
+ : QGraphicsItem(parent)
+ , m_boundingRect(0.0, 0.0, 0.0, 0.0)
+{
+ setFlag(QGraphicsItem::ItemHasNoContents, true);
+ doAppendItems(items, false);
+}
+
+void ContentList::doAppendItems(QList<ContentListItem*> items, bool notify)
+{
+ qreal top = 0;
+ foreach(ContentListItem *item, m_items)
+ top += item->contentHeight();
+ foreach(ContentListItem *item, items) {
+ item->setParentItem(this);
+ item->setPos(0, top);
+ top += item->contentHeight();
+ m_items.append(item);
+ m_boundingRect.setHeight(m_boundingRect.height() + item->contentHeight());
+ if (notify)
+ emit newContentItem(item);
+ }
+}
+
+ContentList::~ContentList()
+{
+ foreach(ContentListActivity *activity, m_queue)
+ activity->deleteLater();
+}
+
+qreal ContentList::width() const
+{
+ return m_boundingRect.width();
+}
+
+void ContentList::setWidth(qreal width)
+{
+ if (m_boundingRect.width() != width) {
+ prepareGeometryChange();
+ m_boundingRect.setWidth(width);
+ update();
+ foreach(ContentListItem *item, m_items)
+ item->updateGeometry();
+ }
+}
+
+QRectF ContentList::boundingRect() const
+{
+ return m_boundingRect;
+}
+
+void ContentList::paint(QPainter *painter, const QStyleOptionGraphicsItem *option, QWidget *widget)
+{
+ Q_UNUSED(painter);
+ Q_UNUSED(option);
+ Q_UNUSED(widget);
+}
+
+bool ContentList::insertItem(int idx, ContentListItem* item)
+{
+ if (getItemIndex(item) != -1)
+ return false;
+ addActivity(new InsertActivity(idx, item, true, *this));
+ return true;
+}
+
+bool ContentList::removeItem(int idx)
+{
+ if (idx < 0 || idx >= m_items.count())
+ return false;
+ addActivity(new RemoveActivity(idx, true, true, *this));
+ return true;
+}
+
+bool ContentList::moveItem(int from, int to)
+{
+ if (from < 0 || from >= m_items.count())
+ return false;
+
+ to = to < 0 ? 0 : to > m_items.count() ? m_items.count() : to;
+
+ if (from == to)
+ return true;
+ addActivity(new MoveActivity(from, to, *this));
+ return true;
+}
+
+void ContentList::addActivity(ContentListActivity *activity)
+{
+ m_queue.append(activity);
+ checkQueue();
+}
+
+void ContentList::checkQueue()
+{
+ while (m_queue.count() > 0 && !m_queue[0]->active())
+ if (!m_queue[0]->run())
+ m_queue.takeFirst()->deleteLater();
+}
+
+void ContentList::activityEnd()
+{
+ m_queue.takeFirst()->deleteLater();
+ checkQueue();
+}
+
+void ContentList::doRemoveItem(int idx, bool notify)
+{
+ if (idx >= 0 && idx < m_items.count()) {
+ ContentListItem *item = m_items[idx];
+ item->setParentItem(0);
+ m_items.removeAt(idx);
+ if (notify) {
+ prepareGeometryChange();
+ m_boundingRect.setHeight(m_boundingRect.height() - item->contentHeight());
+ update();
+ emit contentItemRemoved(item);
+ }
+ }
+}
+
+void ContentList::doInsertItem(int idx, ContentListItem * item, bool notify)
+{
+ if (idx >= 0 && idx <= m_items.count()) {
+ qreal top = 0;
+ for (int i = 0; i < idx; ++i)
+ top += m_items[i]->contentHeight();
+ item->setPos(0, top);
+ item->setParentItem(this);
+ if (notify) {
+ prepareGeometryChange();
+ m_boundingRect.setHeight(m_boundingRect.height() + item->contentHeight());
+ update();
+ emit newContentItem(item);
+ }
+ m_items.insert(idx, item);
+ }
+}
+
+void ContentList::updateItems()
+{
+ qreal top = 0;
+ foreach(ContentListItem *item, m_items) {
+ item->setPos(0, top);
+ top += item->contentHeight();
+ }
+ update();
+}
+
+void ContentList::sortItems(ContentListItemCompare compare)
+{
+ addActivity(new SortActivity(*this, compare));
+}
+
+void ContentList::appendItems(QList<ContentListItem*> items)
+{
+ addActivity(new AppendItemsActivity(*this, items));
+}
diff --git a/weather/contentlist.h b/weather/contentlist.h
new file mode 100644
index 0000000..a1d948e
--- /dev/null
+++ b/weather/contentlist.h
@@ -0,0 +1,262 @@
+/****************************************************************************
+**
+** 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 CONTENTLIST_H
+#define CONTENTLIST_H
+
+#include <QList>
+#include <QObject>
+#include <QGraphicsObject>
+#include <QAbstractAnimation>
+#include <QPointer>
+
+#define ITEM_TOP_PROPERTY_NAME "top"
+
+class ContentListItem : public QObject, public QGraphicsItem
+{
+ Q_OBJECT
+ Q_PROPERTY(qreal top READ getTop WRITE setTop);
+ Q_INTERFACES(QGraphicsItem);
+public:
+ ContentListItem(QGraphicsItem *parent = 0);
+ virtual qreal contentHeight() const = 0;
+ QRectF boundingRect() const;
+
+ virtual QAbstractAnimation *getShowAnimation() = 0;
+ virtual QAbstractAnimation *getHideAnimation() = 0;
+
+protected:
+ void updateGeometry();
+ QVariant itemChange(GraphicsItemChange change, const QVariant &value);
+ void paint(QPainter *painter, const QStyleOptionGraphicsItem *option, QWidget *widget = 0);
+private:
+ friend class ContentList;
+ QRectF m_geometry;
+ qreal getTop() { return pos().y(); }
+ void setTop(qreal top) { setPos(pos().x(), top); }
+
+};
+
+class ContentListActivity;
+class RemoveActivity;
+class InsertActivity;
+
+class ContentList
+ : public QObject
+ , public QGraphicsItem
+{
+ Q_OBJECT
+ Q_INTERFACES(QGraphicsItem);
+public:
+ ContentList(QGraphicsItem *parent = 0);
+ ContentList(QList<ContentListItem*> items, QGraphicsItem *parent = 0);
+ ~ContentList();
+
+ bool insertItem(int idx, ContentListItem*);
+ bool addItem(ContentListItem *item) { return insertItem(m_items.count(), item); }
+ bool removeItem(int idx);
+ bool removeItem(ContentListItem* item) { return removeItem(getItemIndex(item)); }
+ bool moveItem(int from, int to);
+ bool moveItem(ContentListItem* item, int to) { return moveItem(getItemIndex(item), to); }
+ void appendItems(QList<ContentListItem*> items);
+
+ int itemCount() const { return m_items.count(); }
+ ContentListItem* getItem(int idx) { return m_items.at(idx); }
+ int getItemIndex(ContentListItem* item) { return m_items.indexOf(item); }
+
+ bool busy() { return m_queue.count() > 0; }
+ void addActivity(ContentListActivity *);
+
+ void updateItems();
+
+ QRectF boundingRect() const;
+ qreal width() const;
+ void setWidth(qreal width);
+
+signals:
+ void newContentItem(QGraphicsItem *item);
+ void contentItemRemoved(QGraphicsItem *item);
+
+protected:
+ virtual QAbstractAnimation *getInsertAnimation(int idx, qreal height) = 0;
+ virtual QAbstractAnimation *getRemoveAnimation(int idx) = 0;
+ void paint(QPainter *painter, const QStyleOptionGraphicsItem *option, QWidget *widget = 0);
+
+ QList<ContentListItem*> getItems() { return m_items; }
+
+ typedef bool (*ContentListItemCompare)(const ContentListItem* s1, const ContentListItem* s2);
+ void sortItems(ContentListItemCompare compare);
+
+private:
+ friend class ContentListActivity;
+ friend class RemoveActivity;
+ friend class InsertActivity;
+ friend class SortActivity;
+ friend class AppendItemsActivity;
+ QList<ContentListItem*> m_items;
+ QList<ContentListActivity*> m_queue;
+
+ QRectF m_boundingRect;
+
+ void updateGeometry();
+
+ void checkQueue();
+ void activityEnd();
+ void doRemoveItem(int idx, bool notify);
+ void doInsertItem(int idx, ContentListItem* item, bool notify);
+ void doAppendItems(QList<ContentListItem*> items, bool notify);
+
+};
+
+class ContentListActivity : public QObject
+{
+ Q_OBJECT
+public:
+ ContentListActivity(ContentList &list) : m_list(list), m_insertPos(0) {}
+ virtual bool run() = 0;
+ virtual bool active();
+
+protected:
+ ContentList &m_list;
+ void addActivity(ContentListActivity*);
+
+protected slots:
+ void activityEnd() { m_list.activityEnd(); }
+
+private:
+ int m_insertPos;
+
+};
+
+class SignalActivity : public ContentListActivity
+{
+ Q_OBJECT
+public:
+ SignalActivity(ContentList &list) : ContentListActivity(list) {}
+ bool run();
+
+signals:
+ void notify();
+};
+
+class SortActivity : public ContentListActivity
+{
+ Q_OBJECT
+public:
+ SortActivity(ContentList &list, ContentList::ContentListItemCompare compare)
+ : ContentListActivity(list), m_compare(compare) {}
+ bool run();
+
+private:
+ ContentList::ContentListItemCompare m_compare;
+};
+
+class AppendItemsActivity : public ContentListActivity
+{
+ Q_OBJECT
+public:
+ AppendItemsActivity(ContentList &list, QList<ContentListItem*> items)
+ : ContentListActivity(list), m_items(items) {}
+ bool run();
+
+private:
+ QList<ContentListItem*> m_items;
+};
+
+class AnimationActivity : public ContentListActivity
+{
+ Q_OBJECT
+public:
+ AnimationActivity(QAbstractAnimation *animation, ContentList &list);
+ ~AnimationActivity();
+ bool run();
+ bool active() { return m_animation && m_animation->state() != QAbstractAnimation::Stopped; }
+
+private slots:
+ void animationEnd();
+
+private:
+ QAbstractAnimation *m_animation;
+
+};
+
+class RemoveActivity : public ContentListActivity
+{
+ Q_OBJECT
+public:
+ RemoveActivity(int idx, bool destroyItem, bool notify, ContentList &list);
+ bool run();
+ bool active() { return m_active; }
+
+private slots:
+ void hideEnd();
+
+private:
+ int m_idx;
+ bool m_destroyItem;
+ ContentListItem *m_item;
+ bool m_active;
+ const bool m_notify;
+
+};
+
+class InsertActivity : public ContentListActivity
+{
+ Q_OBJECT
+public:
+ InsertActivity(int idx, ContentListItem* item, bool notify, ContentList &list);
+ bool run();
+ bool active() { return m_active; }
+
+private slots:
+ void showItem();
+
+private:
+ int m_idx;
+ QPointer<ContentListItem> m_item;
+ bool m_active;
+ const bool m_notify;
+
+};
+
+class MoveActivity : public ContentListActivity
+{
+public:
+ MoveActivity(int from, int to, ContentList &list);
+ bool run();
+
+private:
+ int m_from;
+ int m_to;
+
+};
+
+#endif // CONTENTLIST_H
diff --git a/weather/weather.pro b/weather/weather.pro
index 218579d..deb28bd 100644
--- a/weather/weather.pro
+++ b/weather/weather.pro
@@ -45,7 +45,9 @@ HEADERS += mainview.h \
forecastdata.h \
bootmanager.h \
pixmapbutton.h \
- scrollbar.h
+ scrollbar.h \
+ contentlist.h \
+ citylist.h
SOURCES += mainview.cpp \
main.cpp \
settings.cpp \
@@ -63,4 +65,6 @@ SOURCES += mainview.cpp \
forecastprovider.cpp \
bootmanager.cpp \
pixmapbutton.cpp \
- scrollbar.cpp
+ scrollbar.cpp \
+ contentlist.cpp \
+ citylist.cpp