diff options
author | Luiz Agostini <luiz.agostini@openbossa.org> | 2009-11-03 21:10:20 -0300 |
---|---|---|
committer | Luiz Agostini <luiz.agostini@openbossa.org> | 2009-11-05 00:42:09 -0300 |
commit | 122ea22c9cb15da3aed92cd720610d69af80ac7c (patch) | |
tree | bccd03ad5dd9efc28553c694f3bc82af207af656 /weather | |
parent | c88a51e51bbb5fa318456826aa63a5fba428f6b3 (diff) |
Weather: city list.
Signed-off-by: Luiz Agostini <luiz.agostini@openbossa.org>
Diffstat (limited to 'weather')
-rw-r--r-- | weather/bootmanager.cpp | 2 | ||||
-rw-r--r-- | weather/citylist.cpp | 398 | ||||
-rw-r--r-- | weather/citylist.h | 143 | ||||
-rw-r--r-- | weather/contentlist.cpp | 439 | ||||
-rw-r--r-- | weather/contentlist.h | 262 | ||||
-rw-r--r-- | weather/weather.pro | 8 |
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 |