diff options
author | Anselmo Lacerda S. de Melo <anselmo.melo@openbossa.org> | 2010-05-29 21:08:09 -0300 |
---|---|---|
committer | Anselmo Lacerda S. de Melo <anselmo.melo@openbossa.org> | 2010-05-30 11:04:33 -0300 |
commit | 75400545f25b394b9c6925ab80ab9fd48e2920e2 (patch) | |
tree | 192b41c59d8e37dff0d486087210a19f37142283 /weather/src | |
parent | 4d86ebb587dc4f822569ecab3d4d25a5b3ca3939 (diff) |
weather: code moved to src/
Signed-off-by: Anselmo Lacerda S. de Melo <anselmo.melo@openbossa.org>
Diffstat (limited to 'weather/src')
62 files changed, 8719 insertions, 0 deletions
diff --git a/weather/src/addcitytool.cpp b/weather/src/addcitytool.cpp new file mode 100644 index 0000000..219d92a --- /dev/null +++ b/weather/src/addcitytool.cpp @@ -0,0 +1,401 @@ +#include "addcitytool.h" +#include "settings.h" +#include "forecastprovider.h" +#include "painttextitem.h" +#include "pixmaploader.h" + +#include <QPainter> +#include <QInputDialog> +#include <QEvent> +#include <QGraphicsScene> +#include <QGraphicsSceneMouseEvent> +#include <QApplication> +#include <QDebug> + +#define ADD_BACKGROUND (PixmapLoader::getPic("background_add_city")) +#define ADD_ERROR_BACKGROUND (PixmapLoader::getPic("background_error_adding")) + +#define CLOSE_BUTTON_PIXMAP (PixmapLoader::getPic("button_list_delete")) +#define ADD_BUTTON_PIXMAP (PixmapLoader::getPic("button_city_send")) +#define BUTTON_LEFT (Settings::scaleWidth(408.0)) + +#define ADD_TEXT_BACKGROUND (PixmapLoader::getPic("textfield_add_city")) +#define ADD_TEXT_LEFT (Settings::scaleWidth(38.0)) + +#define ADD_SCREEN_FONT_SIZE (Settings::scaleHeight(40.0)) + +#define CITY_NAME_FONT_SIZE (Settings::scaleHeight(40.0)) + + +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); +} + +// AddCityScreen + +AddCityScreen::AddCityScreen(const QRectF &boundingRect, QGraphicsItem *parent) + : QGraphicsItem(parent) + , m_boundingRect(boundingRect) +{ +} + +QRectF AddCityScreen::boundingRect () const +{ + return m_boundingRect; +} + +void AddCityScreen::paint(QPainter *painter, const QStyleOptionGraphicsItem *opt, QWidget *widget) +{ + Q_UNUSED(painter); + Q_UNUSED(opt); + Q_UNUSED(widget); +} + +// + +#define ADD_A_CITY_TEXT "Add a city" + +AddCityLineEdit::AddCityLineEdit(QWidget *parent) + : QLineEdit(parent) + , m_clean(true) +{ + editReset(); + connect(this, SIGNAL(textEdited(QString)), this, SLOT(textEditedSlot(QString))); +} + +void AddCityLineEdit::editReset() +{ + m_clean = true; + setText(ADD_A_CITY_TEXT); +} + +void AddCityLineEdit::focusOutEvent(QFocusEvent *event) +{ + if (m_clean) + setText(ADD_A_CITY_TEXT); + QLineEdit::focusOutEvent(event); +} + +void AddCityLineEdit::focusInEvent(QFocusEvent *event) +{ + if (m_clean) + setText(""); + QLineEdit::focusInEvent(event); +} + +void AddCityLineEdit::textEditedSlot(const QString & text) +{ + Q_UNUSED(text); + static const QString cleanText(ADD_A_CITY_TEXT); + if (m_clean && text.length() >= cleanText.length() && + text.left(cleanText.length()) == cleanText) + setText(text.right(text.length() - cleanText.length())); + m_clean = false; +} + +// AddCityFirstScreen + +AddCityFirstScreen::AddCityFirstScreen(const QRectF &boundingRect, QGraphicsItem *parent) + : AddCityScreen(boundingRect, parent) + , m_textBackground(new QGraphicsPixmapItem(ADD_TEXT_BACKGROUND, this)) + , m_button(new PixmapButton(80.0, ADD_BUTTON_PIXMAP, this)) + , m_proxy(new QGraphicsProxyWidget(this)) + , m_lineEdit(new AddCityLineEdit()) +{ + setFlag(ItemHasNoContents, true); + + m_button->setPos(BUTTON_LEFT, getCenterVerticalPos(m_button)); + m_textBackground->setPos(ADD_TEXT_LEFT, getCenterVerticalPos(m_textBackground)); + + + QFont font; + font.setFamily("Nokia Sans"); + font.setPixelSize(CITY_NAME_FONT_SIZE); + font.setStyleStrategy(QFont::PreferAntialias); + m_lineEdit->setFont(font); + + m_lineEdit->setFrame(false); + m_lineEdit->setTextMargins(0, 0, 0, 0); + m_lineEdit->setAttribute(Qt::WA_NoSystemBackground); + m_lineEdit->setStyleSheet("background: transparent; color:white"); + + QRect rect(0, 0, m_textBackground->pixmap().width(), m_textBackground->pixmap().height()); + rect.adjust(5, 5, -5, -5); + m_lineEdit->setGeometry(rect); + + m_proxy->setWidget(m_lineEdit); + m_proxy->setParentItem(m_textBackground); + m_proxy->setPos(5.0, 5.0); + + m_lineEdit->setFocus(); + + connect(m_button, SIGNAL(clicked()), this, SLOT(buttonClick())); +} + +void AddCityFirstScreen::clean() +{ + m_lineEdit->editReset(); +} + +void AddCityFirstScreen::buttonClick() +{ + if (!m_lineEdit->text().isEmpty()) + emit citySelected(m_lineEdit->text()); +} + +// AddCitySearchScreen + +AddCitySearchScreen::AddCitySearchScreen(const QRectF &boundingRect, QGraphicsItem *parent) + : AddCityScreen(boundingRect, parent) + , m_loading(new Loading(this)) +{ + m_loading->hide(); + m_loading->setPos(BUTTON_LEFT, getCenterVerticalPos(m_loading)); + +} + +void AddCitySearchScreen::forecastResponse(int reqId, const ForecastData &forecast) +{ + if (reqId == m_reqId) { + reset(); + if (!forecast.isNull() && !forecast.error()) + emit forecastReceived(forecast); + else + emit forecastRequestError(m_city); + } +} + +void AddCitySearchScreen::setCityName(const QString &name) +{ + m_city = name; + ForecastProvider::connectToResponseSignal(this, SLOT(forecastResponse(int, ForecastData))); + m_reqId = ForecastProvider::getForecast(m_city, false); + m_loading->start(); + m_loading->show(); + update(); +} + +void AddCitySearchScreen::reset() +{ + ForecastProvider::disconnectReceiver(this); + m_loading->stop(); + m_loading->hide(); +} + +void AddCitySearchScreen::paint(QPainter *painter, const QStyleOptionGraphicsItem *opt, QWidget *widget) +{ + Q_UNUSED(opt); + Q_UNUSED(widget); + + TextPainter header(ADD_SCREEN_FONT_SIZE, QColor(255, 255, 255, 102), "Searching for"); + + TextPainter city(ADD_SCREEN_FONT_SIZE, QColor(255, 255, 255, 255), m_city); + city.setQuoted(true); + city.font().setBold(true); + city.setMaxWidth(BUTTON_LEFT); + + qreal top = (boundingRect().height() - header.height() - city.height()) / 2; + top += boundingRect().top(); + + TextPainter::locateAtCenter(&header, 0.0, top, BUTTON_LEFT); + header.paint(painter); + + TextPainter::locateAtCenter(&city, 0.0, top + header.height(), BUTTON_LEFT); + city.paint(painter); +} + +// AddCityErrorScreen + +AddCityErrorScreen::AddCityErrorScreen(QGraphicsItem *parent) + : QGraphicsPixmapItem(ADD_ERROR_BACKGROUND, parent) + , m_button(new PixmapButton(80.0, CLOSE_BUTTON_PIXMAP, this)) +{ + m_button->setPos(BUTTON_LEFT, getCenterVerticalPos(m_button)); + connect(m_button, SIGNAL(clicked()), this, SIGNAL(closed())); +} + +void AddCityErrorScreen::setCityName(const QString &name, ErrorType type) +{ + m_city = name; + m_type = type; + update(); +} + +void AddCityErrorScreen::paint(QPainter *painter, const QStyleOptionGraphicsItem *opt, QWidget *widget) +{ + Q_UNUSED(opt); + Q_UNUSED(widget); + QGraphicsPixmapItem::paint(painter, opt, widget); + if (m_type == NotFound) + paintNotFound(painter); + else + paintAlreadyInList(painter); +} + +void AddCityErrorScreen::paintNotFound(QPainter *painter) +{ + TextPainter header(ADD_SCREEN_FONT_SIZE, QColor(255, 255, 255, 102), "The city"); + TextPainter footer(ADD_SCREEN_FONT_SIZE, QColor(255, 255, 255, 102), "wasn't found"); + + TextPainter city(ADD_SCREEN_FONT_SIZE, QColor(255, 255, 255, 255), m_city); + city.setQuoted(true); + city.font().setBold(true); + city.setMaxWidth(BUTTON_LEFT - header.width() - 5.0); + + qreal top = (boundingRect().height() - city.height() - footer.height()) / 2; + top += boundingRect().top(); + + QList<TextPainter*> topLine; + topLine.append(&header); + topLine.append(&city); + + TextPainter::locateAtCenter(topLine, 0.0, top, BUTTON_LEFT); + header.paint(painter); + city.paint(painter); + + TextPainter::locateAtCenter(&footer, 0.0, top + city.height(), BUTTON_LEFT); + footer.paint(painter); +} + +void AddCityErrorScreen::paintAlreadyInList(QPainter *painter) +{ + TextPainter header(ADD_SCREEN_FONT_SIZE, QColor(255, 255, 255, 102), "The city"); + TextPainter header1(ADD_SCREEN_FONT_SIZE, QColor(255, 255, 255, 102), "is"); + TextPainter footer(ADD_SCREEN_FONT_SIZE, QColor(255, 255, 255, 102), "already on your list"); + + TextPainter city(ADD_SCREEN_FONT_SIZE, QColor(255, 255, 255, 255), m_city); + city.setQuoted(true); + city.font().setBold(true); + city.setMaxWidth(BUTTON_LEFT - header.width() - header1.width() - 10.0); + + qreal top = (boundingRect().height() - city.height() - footer.height()) / 2; + top += boundingRect().top(); + + QList<TextPainter*> topLine; + topLine.append(&header); + topLine.append(&city); + topLine.append(&header1); + + TextPainter::locateAtCenter(topLine, 0.0, top, BUTTON_LEFT); + header.paint(painter); + city.paint(painter); + header1.paint(painter); + + TextPainter::locateAtCenter(&footer, 0.0, top + city.height(), BUTTON_LEFT); + footer.paint(painter); +} + +// AddCityTool + +AddCityTool::AddCityTool(const QList<ForecastData> &content, QGraphicsItem *parent) + : QGraphicsPixmapItem(ADD_BACKGROUND, parent) + , m_content(content) + , m_firstScreen(createFirstScreen()) + , m_SearchScreen(createSearchScreen()) + , m_ErrorScreen(createErrorScreen()) +{ + setCurrentScreen(m_firstScreen); +} + +AddCityTool::~AddCityTool() +{ +} + +int AddCityTool::loadImages() +{ + PixmapLoader::load("background_add_city"); + PixmapLoader::load("background_error_adding"); + PixmapLoader::load("button_list_delete"); + PixmapLoader::load("button_city_send"); + PixmapLoader::load("textfield_add_city"); + return 5; +} + +void AddCityTool::mousePressEvent(QGraphicsSceneMouseEvent *event) +{ + Q_UNUSED(event); +} + +void AddCityTool::cancel() +{ + m_SearchScreen->cancel(); + setCurrentScreen(m_firstScreen); +} + +AddCityFirstScreen *AddCityTool::createFirstScreen() +{ + AddCityFirstScreen *result = new AddCityFirstScreen(boundingRect(), this); + connect(result, SIGNAL(citySelected(QString)), this, SLOT(citySelected(QString))); + result->setPos(0.0, 0.0); + return result; +} + +AddCitySearchScreen *AddCityTool::createSearchScreen() +{ + AddCitySearchScreen *result = new AddCitySearchScreen(boundingRect(), this); + connect(result, SIGNAL(forecastReceived(ForecastData)), + this, SLOT(forecastReceived(ForecastData))); + connect(result, SIGNAL(forecastRequestError(QString)), + this, SLOT(forecastRequestError(QString))); + result->setPos(0.0, 0.0); + return result; +} + +AddCityErrorScreen *AddCityTool::createErrorScreen() +{ + AddCityErrorScreen *result = new AddCityErrorScreen(this); + connect(result, SIGNAL(closed()), this, SLOT(errorScreenClosed())); + result->setPos(0.0, 0.0); + return result; +} + +void AddCityTool::setCurrentScreen(QGraphicsItem *screen) +{ + m_firstScreen->clean(); + m_firstScreen->setVisible(screen == m_firstScreen); + m_SearchScreen->setVisible(screen == m_SearchScreen); + m_ErrorScreen->setVisible(screen == m_ErrorScreen); +} + +void AddCityTool::errorScreenClosed() +{ + setCurrentScreen(m_firstScreen); +} + +void AddCityTool::forecastReceived(const ForecastData &forecast) +{ + for (int i = 0; i < m_content.count(); ++i) + if (m_content[i].key() == forecast.key()) { + m_ErrorScreen->setCityName(forecast.cityName(), AddCityErrorScreen::AlreadyInList); + setCurrentScreen(m_ErrorScreen); + return; + } + emit newForecast(forecast); + setCurrentScreen(m_firstScreen); +} + +void AddCityTool::forecastRequestError(const QString &name) +{ + m_ErrorScreen->setCityName(name, AddCityErrorScreen::NotFound); + setCurrentScreen(m_ErrorScreen); +} + +void AddCityTool::citySelected(const QString &city) +{ + QString text = city.toUpper(); + for (int i = 0; i < m_content.count(); ++i) + if (m_content[i].key().toUpper() == text) { + m_ErrorScreen->setCityName(city, AddCityErrorScreen::AlreadyInList); + setCurrentScreen(m_ErrorScreen); + return; + } + m_SearchScreen->setCityName(city); + setCurrentScreen(m_SearchScreen); +} diff --git a/weather/src/addcitytool.h b/weather/src/addcitytool.h new file mode 100644 index 0000000..f0f6b1f --- /dev/null +++ b/weather/src/addcitytool.h @@ -0,0 +1,153 @@ +#ifndef ADDCITYTOOL_H +#define ADDCITYTOOL_H + +#include <QGraphicsItem> +#include <QGraphicsPixmapItem> +#include <QBasicTimer> +#include <QGraphicsSimpleTextItem> +#include <QPen> +#include <QBrush> +#include <QFont> + #include <QGraphicsProxyWidget> +#include <QLineEdit> +#include <QList> + +#include "pixmapbutton.h" +#include "forecastprovider.h" +#include "loading.h" +#include "forecastdata.h" + +class AddCityScreen : public QGraphicsItem +{ +public: + AddCityScreen(const QRectF &boundingRect, QGraphicsItem *parent = 0); + + QRectF boundingRect () const; + void paint(QPainter *painter, const QStyleOptionGraphicsItem *option, QWidget *widget = 0); + +private: + const QRectF m_boundingRect; +}; + +class AddCityLineEdit : public QLineEdit +{ + Q_OBJECT +public: + AddCityLineEdit(QWidget *parent = 0); + void editReset(); +protected: + void focusInEvent(QFocusEvent *event); + void focusOutEvent(QFocusEvent *event); +private: + bool m_clean; +private slots: + void textEditedSlot(const QString & text); +}; + + +class AddCityFirstScreen : public QObject, public AddCityScreen +{ + Q_OBJECT +public: + AddCityFirstScreen(const QRectF &boundingRect, QGraphicsItem *parent = 0); + void clean(); + +signals: + void citySelected(const QString &city); + +private: + QGraphicsPixmapItem * const m_textBackground; + PixmapButton * const m_button; + QString m_text; + QGraphicsProxyWidget *m_proxy; + AddCityLineEdit *m_lineEdit; + +private slots: + void buttonClick(); +}; + +class AddCitySearchScreen : public QObject , public AddCityScreen +{ + Q_OBJECT +public: + AddCitySearchScreen(const QRectF &boundingRect, QGraphicsItem *parent = 0); + void paint(QPainter *painter, const QStyleOptionGraphicsItem *option, QWidget *widget = 0); + void setCityName(const QString &name); + void cancel() { reset(); } + +signals: + void forecastReceived(const ForecastData &forecast); + void forecastRequestError(const QString &name); + +private: + QString m_city; + int m_reqId; + Loading *m_loading; + + void reset(); + +private slots: + void forecastResponse(int reqId, const ForecastData &forecast); +}; + +class AddCityErrorScreen : public QObject, public QGraphicsPixmapItem +{ + Q_OBJECT +public: + enum ErrorType + { + NotFound, + AlreadyInList + }; + AddCityErrorScreen(QGraphicsItem *parent = 0); + void paint(QPainter *painter, const QStyleOptionGraphicsItem *option, QWidget *widget = 0); + void setCityName(const QString &name, ErrorType type); + +signals: + void closed(); + +private: + PixmapButton * const m_button; + QString m_city; + ErrorType m_type; + + void paintNotFound(QPainter *painter); + void paintAlreadyInList(QPainter *painter); +}; +class AddCityTool : public QObject, public QGraphicsPixmapItem +{ + Q_OBJECT +public: + AddCityTool(const QList<ForecastData> &content, QGraphicsItem *parent = 0); + ~AddCityTool(); + void cancel(); + + static int loadImages(); + +private: + const QList<ForecastData> &m_content; + AddCityFirstScreen * const m_firstScreen; + AddCitySearchScreen * const m_SearchScreen; + AddCityErrorScreen * const m_ErrorScreen; + + AddCityFirstScreen *createFirstScreen(); + AddCitySearchScreen *createSearchScreen(); + AddCityErrorScreen *createErrorScreen(); + + void setCurrentScreen(QGraphicsItem *screen); + +protected: + void mousePressEvent(QGraphicsSceneMouseEvent *event); + +signals: + void newForecast(const ForecastData &forecast); + +private slots: + void errorScreenClosed(); + void forecastReceived(const ForecastData &forecast); + void forecastRequestError(const QString &name); + void citySelected(const QString &city); + +}; + +#endif // ADDCITYTOOL_H diff --git a/weather/src/bootmanager.cpp b/weather/src/bootmanager.cpp new file mode 100644 index 0000000..ac41a8f --- /dev/null +++ b/weather/src/bootmanager.cpp @@ -0,0 +1,69 @@ +#include "bootmanager.h" +#include "settings.h" +#include "forecastprovider.h" +#include "pixmaploader.h" +#include "forecasthungitem.h" +#include "forecastrain.h" +#include "forecastsnow.h" +#include "forecaststars.h" +#include "cityinfodisplay.h" +#include "titlebar.h" +#include "citycarroussel.h" +#include "scrollbar.h" +#include "citylist.h" +#include "addcitytool.h" + +#include <QDebug> + +BootManager::BootManager(QObject *parent) + : QObject(parent) + , m_imagesLoaded(false) +{ +} + +void BootManager::run(const QStringList &locations) +{ + m_keys = locations; + ForecastProvider::connectToResponseSignal(this, SLOT(forecastResponse(int, ForecastData))); + foreach (const QString & city, m_keys) { + m_requests.append(ForecastProvider::getForecast(city, true)); + m_data.append(ForecastData()); + } + PixmapLoader::connectToOnIdleSignal(this, SLOT(pixmapLoaderIsIdle())); + int count = ForecastHungItem::loadImages(); + count += ForecastRain::loadImages(); + count += ForecastSnow::loadImages(); + count += ForecastStars::loadImages(); + count += CityInfoDisplay::loadImages(); + count += TitleBar::loadImages(); + count += CityCarroussel::loadImages(); + count += ScrollBar::loadImages(); + count += CityList::loadImages(); + count += AddCityTool::loadImages(); + + m_imagesLoaded = count == 0; + if (m_imagesLoaded) + PixmapLoader::disconnectReceiver(this); +} + +void BootManager::forecastResponse(int reqId, const ForecastData &forecast) +{ + if (m_requests.removeAll(reqId)) { + int idx = m_keys.indexOf(forecast.key()); + if (idx >= 0) + m_data[idx] = forecast; + } + if (m_requests.isEmpty()) { + ForecastProvider::disconnectReceiver(this); + if (m_imagesLoaded) + emit ready(); + } +} + +void BootManager::pixmapLoaderIsIdle() +{ + PixmapLoader::disconnectReceiver(this); + m_imagesLoaded = true; + if (m_requests.isEmpty()) + emit ready(); +} diff --git a/weather/src/bootmanager.h b/weather/src/bootmanager.h new file mode 100644 index 0000000..4fc4969 --- /dev/null +++ b/weather/src/bootmanager.h @@ -0,0 +1,31 @@ +#ifndef BOOTMANAGER_H +#define BOOTMANAGER_H + +#include <QObject> +#include <QMap> +#include <QStringList> +#include "forecastdata.h" + +class BootManager : public QObject +{ + Q_OBJECT +public: + BootManager(QObject *parent = 0); + void run(const QStringList &locations); + QList<ForecastData> data() const { return m_data; } + +signals: + void ready(); + +private slots: + void forecastResponse(int reqId, const ForecastData &forecast); + void pixmapLoaderIsIdle(); + +private: + QStringList m_keys; + QList<ForecastData> m_data; + QList<int> m_requests; + bool m_imagesLoaded; +}; + +#endif // BOOTMANAGER_H diff --git a/weather/src/carroussel.h b/weather/src/carroussel.h new file mode 100644 index 0000000..983cbc8 --- /dev/null +++ b/weather/src/carroussel.h @@ -0,0 +1,156 @@ +/**************************************************************************** +** +** 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 __CARROUSSEL_H__ +#define __CARROUSSEL_H__ + +#include <QList> +#include <QDebug> + + +template<class T> class Carroussel +{ +public: + Carroussel() : m_pos(0) {} + ~Carroussel() {} + int count() const { return m_list.count(); } + int pos() const { return m_pos; } + void move(int offset) { m_pos = getIndex(m_pos + offset); } + int add(const T &item); + void remove(int idx); + T &operator[](int aIdx) { return m_list[getIndex(aIdx + m_pos)]; } + const T &operator[](int aIdx) const { return m_list[getIndex(aIdx + m_pos)]; } + void reset(const QList<T> &list, int newPos); +private: + int m_pos; + QList<T> m_list; + int getIndex(int idx) const; + int getCarrousselIndex(int idx); +}; + +class CarrousselGroup +{ +public: + inline ~CarrousselGroup(); + template<class T> void add(T *carroussel); + inline void remove(void *carroussel); + inline void move(int offset); + +private: + class BasicCarrosselHandler + { + public: + virtual ~BasicCarrosselHandler() {} + virtual void move(int offset) = 0; + virtual bool isEqual(void *value) = 0; + }; + template<class T> class CarrousselHandler : public BasicCarrosselHandler + { + public: + CarrousselHandler(T *carroussel) : m_carroussel(carroussel) {} + void move(int offset) { m_carroussel->move(offset); } + bool isEqual(void *value) { return m_carroussel == value; } + private: + T *m_carroussel; + }; + + QList<BasicCarrosselHandler*> m_items; + inline int find(void *value); +}; + +// Carroussel + +template<class T> int Carroussel<T>::add(const T &item) +{ + int idx = m_list.count() - m_pos; + m_list.append(item); + return idx <= m_list.count() / 2 ? idx : idx - m_list.count(); +} + +template<class T> void Carroussel<T>::remove(int idx) +{ + idx = getIndex(m_pos + idx); + m_list.removeAt(idx); + m_pos = getIndex(idx < m_pos ? m_pos - 1 : m_pos); +} + +template<class T> void Carroussel<T>::reset(const QList<T> &list, int newPos) +{ + m_list = list; + m_pos = getIndex(newPos); +} + +template<class T> int Carroussel<T>::getIndex(int idx) const +{ + if (m_list.count() == 0) + return 0; + idx %= m_list.count(); + return idx >= 0 ? idx : m_list.count() + idx; +} + +// CarrousselGroup + +CarrousselGroup::~CarrousselGroup() +{ + qDeleteAll(m_items); +} + +template<class T> void CarrousselGroup::add(T *carroussel) +{ + if (find(carroussel) == -1) + m_items.append(new CarrousselHandler<T>(carroussel)); +} + +void CarrousselGroup::remove(void *carroussel) +{ + int idx = find(carroussel); + if (idx >= 0) + delete m_items.takeAt(idx); +} + +int CarrousselGroup::find(void *value) +{ + for (int i = 0; i < m_items.count(); ++i) + if (m_items[i]->isEqual(value)) + return i; + return -1; +} + +void CarrousselGroup::move(int offset) +{ + foreach (BasicCarrosselHandler *handler, m_items) + handler->move(offset); +} + + + + +#endif /* __CARROUSSEL_H__ */ diff --git a/weather/src/citycarroussel.cpp b/weather/src/citycarroussel.cpp new file mode 100644 index 0000000..3f2d5a3 --- /dev/null +++ b/weather/src/citycarroussel.cpp @@ -0,0 +1,472 @@ +/**************************************************************************** +** +** 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 "citycarroussel.h" +#include "settings.h" +#include "pixmaploader.h" +#include "titlebar.h" + +#include <QFont> + +struct ForecastEnvironment +{ + QPixmap picture; + QPixmap effect; + QString description; +}; + +struct ForecastBackgroundData +{ + const Forecast::ForecastType type; + const char * nightName; + const char* dayName; + const Forecast::Effect effect; + const char* description; + + static ForecastEnvironment getEnvironment(Forecast::ForecastType forecast, bool night); + +private: + QString picName(bool night) const { return night ? nightName : dayName; } + +}; + +static ForecastBackgroundData BackgroundData[Forecast::UnknownForecast + 1] = { + {Forecast::MostlyCloudy, "bg_night_rain", "bg_day_rain", Forecast::NoEffect, "mostly cloudy"}, + {Forecast::Cloudy, "bg_night_rain", "bg_day_rain", Forecast::NoEffect, "cloudy"}, + {Forecast::MostlySunny, "bg_night_clear", "bg_day_clear", Forecast::NoEffect, "mostly sunny"}, + {Forecast::PartlyCloudy, "bg_night_clear", "bg_day_clear", Forecast::NoEffect, "partially cloudy"}, + {Forecast::Sunny, "bg_night_clear", "bg_day_clear", Forecast::NoEffect, "sunny"}, + {Forecast::Flurries, "bg_night_rain", "bg_day_rain", Forecast::NoEffect, "flurries"}, + {Forecast::Fog, "bg_night_rain", "bg_day_heavyrain", Forecast::FogEffect, "fog"}, + {Forecast::Haze, "bg_night_rain", "bg_day_heavyrain", Forecast::HazeEffect, "haze"}, + {Forecast::Sand, "bg_night_rain", "bg_day_heavyrain", Forecast::HazeEffect, "sand"}, + {Forecast::Dust, "bg_night_rain", "bg_day_heavyrain", Forecast::HazeEffect, "dust"}, + {Forecast::Icy, "bg_night_rain", "bg_day_heavyrain", Forecast::NoEffect, "icy"}, + {Forecast::Sleet, "bg_night_rain", "bg_day_heavyrain", Forecast::NoEffect, "sleet"}, + {Forecast::ChanceOfSleet, "bg_night_rain", "bg_day_heavyrain", Forecast::NoEffect, "chance of sleet"}, + {Forecast::Snow, "bg_night_rain", "bg_day_heavyrain", Forecast::NoEffect, "snow"}, + {Forecast::ChanceOfSnow, "bg_night_rain", "bg_day_heavyrain", Forecast::NoEffect, "chance of snow"}, + {Forecast::Mist, "bg_night_rain", "bg_day_rain", Forecast::NoEffect, "mist"}, + {Forecast::Rain, "bg_night_rain", "bg_day_heavyrain", Forecast::NoEffect, "rain"}, + {Forecast::ChanceOfRain, "bg_night_rain", "bg_day_heavyrain", Forecast::NoEffect, "chance of rain"}, + {Forecast::Storm, "bg_night_rain", "bg_day_heavyrain", Forecast::NoEffect, "storm"}, + {Forecast::ChanceOfStorm, "bg_night_rain", "bg_day_heavyrain", Forecast::NoEffect, "chance of storm"}, + {Forecast::Thunderstorm, "bg_night_rain", "bg_day_heavyrain", Forecast::NoEffect, "thunderstorm"}, + {Forecast::ChanceOfThunderstorm, "bg_night_rain", "bg_day_heavyrain", Forecast::NoEffect, "chance of thunderstorm"}, + {Forecast::UnknownForecast, "bg_night_rain", "bg_night_rain", Forecast::NoEffect, "unknown"} +}; + + +ForecastEnvironment ForecastBackgroundData::getEnvironment(Forecast::ForecastType forecast, + bool night) +{ + ForecastEnvironment result; + for (int i = 0; i < Forecast::UnknownForecast; ++i) { + if (forecast == BackgroundData[i].type) { + result.picture = PixmapLoader::getPic(BackgroundData[i].picName(night)); + + const Forecast::Effect effect = BackgroundData[i].effect; + const QString name = effect == Forecast::HazeEffect ? "haze" + : effect == Forecast::FogEffect ? "fog" + : QString(); + result.effect = name.isNull() ? QPixmap() : PixmapLoader::getPic(name); + + result.description = BackgroundData[i].description; + if (night) + result.description.append(" at night"); + } + } + return result; +} + +// ForecastBackground + +ForecastBackground::ForecastBackground(QGraphicsItem *parent) + : QGraphicsPixmapItem(parent) + , m_forecast(Forecast::UnknownForecast) + , m_night(false) + , m_effect(0) +{ + setShapeMode(QGraphicsPixmapItem::BoundingRectShape); + setFlag(QGraphicsItem::ItemSendsGeometryChanges, true); +} + +void ForecastBackground::setForecast(Forecast::ForecastType forecast, bool night) +{ + if (forecast == m_forecast && night == m_night) + return; + m_forecast = forecast; + m_night = night; + ForecastEnvironment environment = ForecastBackgroundData::getEnvironment(m_forecast, m_night); + setPixmap(environment.picture); + m_description = environment.description; + + if (environment.effect.isNull()) { + delete m_effect; + m_effect = 0; + } + else { + if (!m_effect) { + m_effect = new QGraphicsPixmapItem(this); + m_effect->setZValue(20.0); + m_effect->setPos(0.0, 0.0); + } + m_effect->setPixmap(environment.effect); + } +} + +void ForecastBackground::setReferencePos(qreal pos) +{ + m_pos = pos; + m_displacement = 0; + setPos(m_pos, 0.0); +} + +void ForecastBackground::setDisplacement(qreal displacement) +{ + m_displacement = displacement; + setPos(m_pos + m_displacement, 0.0); +} + + +void ForecastBackground::addCompanion(QGraphicsItem *item) +{ + if (m_companion.indexOf(item) == -1) + m_companion.append(item); + +} + +void ForecastBackground::removeCompanion(QGraphicsItem *item) +{ + m_companion.removeAll(item); +} + +QVariant ForecastBackground::itemChange(GraphicsItemChange change, const QVariant &value) +{ + if (change == ItemPositionChange) { + const QPointF move = value.toPointF() - pos(); + for (int i = 0; i < m_companion.count(); ++i) + m_companion[i]->moveBy(move.x(), move.y()); + } + return QGraphicsPixmapItem::itemChange(change, value); +} + +// CityCarrousselGesture + +CityCarrousselGesture::CityCarrousselGesture(CityCarroussel &carroussel, QGraphicsItem *parent) + : GestureBox(parent) + , m_carroussel(carroussel) + , m_active(true) + , m_aborted(false) + , m_startPoint(0.0) +{ +} + +void CityCarrousselGesture::gestureMousePress(QPointF pos, bool &startGesture, bool &acceptClick) +{ + Q_UNUSED(pos); + Q_UNUSED(acceptClick); + startGesture = m_carroussel.active(); +} + +void CityCarrousselGesture::gestureStart(QPointF pos) +{ + m_startPoint = pos.x(); + m_aborted = false; +} + +void CityCarrousselGesture::gestureMove(QPointF pos, QPointF movement, QPointF speed) +{ + Q_UNUSED(movement); + Q_UNUSED(speed); + if (!m_aborted) + m_carroussel.setGestureDisplacement(pos.x() - m_startPoint); +} + +void CityCarrousselGesture::gestureEnd(QPointF pos, QPointF speed) +{ + Q_UNUSED(pos); + Q_UNUSED(speed); + if (!m_aborted) + m_carroussel.move(0); +} + +// CityCarroussel + +static const qreal transparencyRef = 58.0 / 480.0; +static const qreal marginRef = 47.0 / 480.0; +static const qreal backgroundPosRef = -(transparencyRef + marginRef); +static const qreal backgroundWidthRef = 635.0 / 480.0; + +CityCarroussel::CityCarroussel(QGraphicsItem *parent) + : QGraphicsItem(parent) + , m_view(0) + , m_displacement(0.0) + , m_gestureBox(new CityCarrousselGesture(*this, this)) + , m_boundingRect(QPointF(0.0, 0.0), Settings::windowSize()) + , m_backgroundWidth(backgroundWidthRef * m_boundingRect.width()) + , m_backgroundPos(backgroundPosRef * m_boundingRect.width()) + , m_transparencySize(transparencyRef * m_boundingRect.width()) + , m_distance(m_backgroundWidth - m_transparencySize) + , m_active(true) + , m_deleteAfterMove(false) +{ + m_gestureBox->setRect(boundingRect()); + m_carroussel.add(&m_data); + m_carroussel.add(&m_background); + m_carroussel.add(&m_cityInfo); + + TitleBar *titleBar = new TitleBar(this); + titleBar->setPos(0.0, 0.0); + titleBar->setZValue(100.0); + + QPixmap barPic = PixmapLoader::getPic("city_name_background_bigger"); + QGraphicsPixmapItem * cityBar = new QGraphicsPixmapItem(barPic, this); + cityBar->setPos(Settings::scaleWidth(-20.0), Settings::scaleHeight(735.0)); + cityBar->setZValue(10.0); + + m_positions[0] = m_backgroundPos - m_distance; + m_positions[1] = m_backgroundPos; + m_positions[2] = m_backgroundPos + m_distance; + + for (int i = -1; i <= 1; ++i) { + ForecastBackground *background = new ForecastBackground(this); + m_background.add(background); + + CityInfoDisplay *city = new CityInfoDisplay(this); + m_cityInfo.add(city); + city->setPos(background->pos().x() - m_backgroundPos + Settings::windowSize().width() / 2, + background->pos().y() + Settings::scaleHeight(525.0)); + city->setZValue(11.0); + background->addCompanion(city); + } + + updateBackground(-1); + updateBackground(0); + updateBackground(1); +} + +CityCarroussel::~CityCarroussel() +{ +} + +int CityCarroussel::loadImages() +{ + for (int i = 0; i < Forecast::UnknownForecast;++i) { + PixmapLoader::load(BackgroundData[i].nightName); + PixmapLoader::load(BackgroundData[i].dayName); + } + PixmapLoader::load("haze"); + PixmapLoader::load("fog"); + PixmapLoader::load("city_name_background_bigger"); + return Forecast::UnknownForecast + 3; + +} + +void CityCarroussel::setActive(bool value) +{ + if (value == m_active) + return; + m_active = value; + if (m_active) + connect(m_cityInfo[0], SIGNAL(nameClicked()), this, SIGNAL(cityNameClicked())); + else + disconnect(m_cityInfo[0]); +} + +QRectF CityCarroussel::boundingRect () const +{ + return m_boundingRect; +} + +void CityCarroussel::paint(QPainter *painter, const QStyleOptionGraphicsItem *opt, QWidget *widget) +{ + Q_UNUSED(painter); + Q_UNUSED(opt); + Q_UNUSED(widget); +} + +void CityCarroussel::add(ForecastData item) +{ + int idx = m_data.add(item); + if (idx < -1 || idx > 1) + return; + if (m_data.count()< 3) { + updateBackground(-1); + updateBackground(0); + updateBackground(1); + } else + updateBackground(idx); + if (idx == 0) + updateMainItem(); +} + +void CityCarroussel::update(QList<ForecastData> items) +{ + if (items.isEmpty()) + return; + + bool wasEmpty = m_data.count() == 0; + bool transitionNeeded = false; + + if (!wasEmpty) { + for (int i = 0; i < items.count(); ++i) { + if (items[i].key() == m_data[0].key()) { + for (int j = 0; j < i; ++j) + items.append(items.takeFirst()); + break; + } + } + transitionNeeded = m_data[0] != items[0]; + if (transitionNeeded) + items.push_front(m_data[0]); + } + + m_data.reset(items, 0); + updateBackground(-1); + updateBackground(1); + + if (wasEmpty) { + updateBackground(0); + updateMainItem(); + } + if (transitionNeeded) { + move(-1, true); + } +} + +void CityCarroussel::updateMainItem() +{ + delete m_view; + m_view = ForecastView::createView(m_data[0].type(), m_data[0].night()); + if (m_view) { + connect(m_cityInfo[0], SIGNAL(nameClicked()), this, SIGNAL(cityNameClicked())); + m_view->setPos(-m_positions[1], 0.0); + m_view->setZValue(10.0); + m_view->setParentItem(m_background[0]); + m_view->reset(); + + QAbstractAnimation * animation = m_view->getAnimation(); + if (animation) + animation->start(QAbstractAnimation::DeleteWhenStopped); + } +} + +void CityCarroussel::updateBackground(int idx) +{ + if (m_data.count() == 0) + m_background[idx]->reset(); + else + m_background[idx]->setForecast(m_data[idx].type(), m_data[idx].night()); + m_background[idx]->setZValue(idx); + m_background[idx]->setReferencePos(m_positions[idx + 1]); + QString cityName = m_data.count() ? m_data[idx].cityName() : QString("unknown"); + m_cityInfo[idx]->setCityName(cityName); + if (m_data.count() > 0) + m_cityInfo[idx]->setTemperature(m_data[idx].lower(), + m_data[idx].upper(), m_data[idx].current()); + else + m_cityInfo[idx]->setTemperature(0, 0, 0); +} + +void CityCarroussel::moveEnd(int direction) +{ + m_displacement = 0.0; + m_gestureBox->setActive(true); + if (direction) { + m_carroussel.move(-direction); + for (int i = -1; i <= 1; ++i) { + m_background[i]->setReferencePos(m_positions[i + 1]); + m_background[i]->setZValue(i); + } + if (m_deleteAfterMove) { + m_data.remove(direction); + updateBackground(direction); + } + + updateBackground(-direction); + updateMainItem(); + } +} + +void CityCarroussel::setDisplacement(qreal displacement) +{ + m_displacement = displacement; + for (int i = -1; i <= 1; ++i) + m_background[i]->setDisplacement(m_displacement); + if (m_view) + m_view->setElementsDisplacement(m_displacement / m_distance); +} + +void CityCarroussel::setGestureDisplacement(qreal displacement) +{ + if (qAbs(displacement) > boundingRect().width() * 0.5) { + move(displacement < 0 ? -1 : 1); + } + else + setDisplacement(displacement); +} + +void CityCarroussel::move(int direction, bool deleteLastItem) +{ + m_gestureBox->setActive(false); + m_gestureBox->abort(); + disconnect(m_cityInfo[0]); + m_deleteAfterMove = deleteLastItem; + + QPropertyAnimation *animation = new QPropertyAnimation(this, "displacement"); + + animation->setEndValue(direction * m_distance); + animation->setEasingCurve(QEasingCurve::OutQuart); + animation->setDuration(500); + + switch(direction) { + case -1: + connect(animation, SIGNAL(finished()), this, SLOT(moveLeftEnd())); + break; + case 0: + connect(animation, SIGNAL(finished()), this, SLOT(moveBackEnd())); + break; + case 1: + connect(animation, SIGNAL(finished()), this, SLOT(moveRightEnd())); + break; + } + animation->start(QAbstractAnimation::DeleteWhenStopped); +} + + + + + + + + + diff --git a/weather/src/citycarroussel.h b/weather/src/citycarroussel.h new file mode 100644 index 0000000..8bea25b --- /dev/null +++ b/weather/src/citycarroussel.h @@ -0,0 +1,168 @@ +/**************************************************************************** +** +** 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 CITYCARROUSSEL_H +#define CITYCARROUSSEL_H + +#include "forecast.h" +#include "forecastview.h" +#include "carroussel.h" +#include "gesturebox.h" +#include "cityinfodisplay.h" +#include "forecastdata.h" + +#include <QSharedPointer> +#include <QWeakPointer> + +class ForecastBackground : public QGraphicsPixmapItem +{ +public: + ForecastBackground(QGraphicsItem *parent = 0); + Forecast::ForecastType forecast() const { return m_forecast; } + bool night() const { return m_night; } + + void setForecast(Forecast::ForecastType forecast, bool night); + void reset() { setForecast(Forecast::UnknownForecast, false); } + + void setReferencePos(qreal pos); + void setDisplacement(qreal displacement); + + void addCompanion(QGraphicsItem *item); + void removeCompanion(QGraphicsItem *item); + + QString description() const { return m_description; } + +private: + QVariant itemChange(GraphicsItemChange change, const QVariant &value); + +private: + Forecast::ForecastType m_forecast; + bool m_night; + qreal m_pos; + qreal m_displacement; + QList<QGraphicsItem*> m_companion; + + QGraphicsPixmapItem *m_effect; + QString m_description; + +}; + +class CityCarroussel; + +class CityCarrousselGesture : public GestureBox +{ +public: + CityCarrousselGesture(CityCarroussel &carroussel, QGraphicsItem *parent = 0); + void setActivate(bool active) { m_active = active; } + void abort() { m_aborted = true; } + +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); + +private: + CityCarroussel &m_carroussel; + bool m_active; + bool m_aborted; + qreal m_startPoint; + +}; + +class CityCarroussel : public QObject, public QGraphicsItem +{ + Q_OBJECT + Q_INTERFACES(QGraphicsItem) + Q_PROPERTY(qreal displacement READ displacement WRITE setDisplacement) +public: + CityCarroussel(QGraphicsItem *parent = 0); + ~CityCarroussel(); + void add(ForecastData item); + + void update(QList<ForecastData> items); + + static int loadImages(); + + bool active() const { return m_active; } + void setActive(bool value); + + QString selected() const { return m_data.count() > 0 ? m_data[0].key() : QString(); } + + QRectF boundingRect () const; + void paint(QPainter *painter, const QStyleOptionGraphicsItem *option, QWidget *widget = 0); + +signals: + void cityNameClicked(); + +public slots: + void moveLeft() { move(-1); } + void moveRight() { move(1); } + +private: + friend class CityCarrousselGesture; + + Carroussel<ForecastData> m_data; + Carroussel<ForecastBackground*> m_background; + Carroussel<CityInfoDisplay*> m_cityInfo; + + CarrousselGroup m_carroussel; + ForecastView *m_view; + qreal m_displacement; + CityCarrousselGesture *m_gestureBox; + const QRectF m_boundingRect; + const qreal m_backgroundWidth; + const qreal m_backgroundPos; + const qreal m_transparencySize; + const qreal m_distance; + qreal m_positions[3]; + + bool m_active; + bool m_deleteAfterMove; + + void setGestureDisplacement(qreal displacement); + void moveBack() { move(0); } + void move(int direction, bool deleteLastItem = false); + void moveEnd(int direction); + + void updateBackground(int idx); + void updateMainItem(); + + void setDisplacement(qreal displacement); + qreal displacement() const { return m_displacement; } + +private slots: + void moveLeftEnd() { moveEnd(-1); } + void moveRightEnd() { moveEnd(1); } + void moveBackEnd() { moveEnd(0); } +}; + +#endif // CITYCARROUSSEL_H diff --git a/weather/src/cityinfodisplay.cpp b/weather/src/cityinfodisplay.cpp new file mode 100644 index 0000000..570ef8a --- /dev/null +++ b/weather/src/cityinfodisplay.cpp @@ -0,0 +1,323 @@ +/**************************************************************************** +** +** 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 "cityinfodisplay.h" +#include "settings.h" +#include "pixmaploader.h" + +#include <QFont> +#include <QPen> +#include <QFontMetrics> +#include <QDebug> +#include <QPainter> +#include <QGraphicsSceneMouseEvent> + +#define CURRENT_TEMP_FONT_SIZE (Settings::scaleHeight(170.0)) +#define TEMP_BOUND_FONT_SIZE (Settings::scaleHeight(40.0)) +#define CITY_NAME_FONT_SIZE (Settings::scaleHeight(50.0)) +#define MAX_CITY_NAME_WIDTH (Settings::scaleWidth(440.0)) +#define CITY_NAME_TOP (Settings::scaleHeight(235.0)) + +// CurrentTemperatureDisplay + +CurrentTemperatureDisplay::CurrentTemperatureDisplay() + : m_brush(Qt::white) + , m_sign(PixmapLoader::getPic("minus_sign")) + , m_unit(PixmapLoader::getPic("centigrades")) + , m_value(-1) + , m_pos(0.0, 0.0) +{ + m_font.setFamily("Nokia Sans"); + m_font.setBold(true); + m_font.setPixelSize(CURRENT_TEMP_FONT_SIZE); + m_font.setStyleStrategy(QFont::PreferAntialias); + + m_pen.setColor(Qt::white); + m_pen.setBrush(Qt::white); + m_pen.setJoinStyle(Qt::RoundJoin); + + setTemperature(0); +} + +QRectF CurrentTemperatureDisplay::boundingRect () const +{ + return m_boundingRect; +} + +void CurrentTemperatureDisplay::paint(QPainter *painter) +{ + if (m_value < 0) + painter->drawPixmap(m_signPos, m_sign); + + painter->setPen(m_pen); + painter->setBrush(m_brush); + painter->setFont(m_font); + painter->drawText(m_numberPos, m_text); + + painter->drawPixmap(m_unitPos, m_unit); +} + +void CurrentTemperatureDisplay::setTemperature(int value) +{ + static const int maxValue = 300; + value = qMax(qMin(maxValue, value), -maxValue); + + if (m_value == value) + return; + + m_value = value; + m_text = QString::number(qAbs(m_value)); + + QFontMetrics metrics(m_font); + + int textWidth = metrics.width(m_text); + int width = textWidth; + int left = 0; + + if (m_value < 0) { + left = -(width >> 1); + m_numberPos = QPointF(left, m_unit.height()); + width += m_sign.width(); + left -= m_sign.width(); + m_signPos = QPointF(left, 0.0); + m_unitPos = QPointF(left + width, 0.0); + width += m_unit.width(); + } else { + width += m_unit.width(); + int left = -(width >> 1); + m_numberPos = QPointF(left, m_unit.height()); + m_unitPos = QPointF(left + textWidth, 0.0); + } + m_boundingRect = QRectF(left, 0.0, width, m_unit.height()); + +} + +// TemperatureBoundDisplay + +TemperatureBoundDisplay::TemperatureBoundDisplay(bool lowerBound) + : m_brush(Qt::white) + , m_icon(PixmapLoader::getPic(lowerBound ? "icon_min" : "icon_max")) + , m_value(-1) + , m_lowerBound(lowerBound) + , m_pos(0.0, 0.0) +{ + m_font.setFamily("Nokia Sans"); + m_font.setPixelSize(TEMP_BOUND_FONT_SIZE); + m_font.setStyleStrategy(QFont::PreferAntialias); + + QColor color(255, 255, 255, 178); + + m_pen.setColor(color); + m_pen.setBrush(color); + m_pen.setJoinStyle(Qt::RoundJoin); + + m_brush.setColor(color); + + setValue(0); +} + +QRectF TemperatureBoundDisplay::boundingRect () const +{ + return m_boundingRect; +} + +void TemperatureBoundDisplay::paint(QPainter *painter) +{ + QRectF rect(m_boundingRect); + painter->drawPixmap(rect.topLeft(), m_icon); + rect.setLeft(rect.left() + m_icon.width()); + + painter->setPen(m_pen); + painter->setBrush(m_brush); + painter->setFont(m_font); + QPointF textPos(rect.left(), m_icon.height()); + painter->drawText(textPos, m_text); +} + +void TemperatureBoundDisplay::setValue(int value) +{ + static const int maxValue = 300; + value = qMax(qMin(maxValue, value), -maxValue); + + if (m_value == value) + return; + + m_value = value; + m_text = QString::number(m_value) + QChar(176) + 'c'; + + QFontMetrics metrics(m_font); + m_boundingRect = QRectF(0.0, 0.0, m_icon.width() + metrics.width(m_text), m_icon.height()); +} + +// TemperatureDisplay + +TemperatureDisplay::TemperatureDisplay() + : m_line(PixmapLoader::getPic("division_line")) + , m_lowerBound(true) + , m_upperBound(false) + , m_pos(0.0, 0.0) +{ + setTemperature(0, 0, 0); +} + +void TemperatureDisplay::setTemperature(int lowerBound, int upperBound, int current) +{ + m_lowerBound.setValue(lowerBound); + m_upperBound.setValue(upperBound); + m_current.setTemperature(current); + + m_current.setPos(0.0, 0.0); + + const qreal lineRight = m_line.width() >> 1; + const qreal right = qMax(lineRight, m_current.boundingRect().right()); + const qreal left = qMin(-lineRight, m_current.boundingRect().left()); + + qreal height = m_current.boundingRect().height() + 8.0; + + m_linePos = QPointF(-lineRight, height); + height += m_line.height() + 8.0; + + m_upperBound.setPos(-lineRight, height); + m_lowerBound.setPos(lineRight - m_lowerBound.boundingRect().width(), height); + + height += qMax(m_lowerBound.boundingRect().height(), m_upperBound.boundingRect().height()); + + m_boundingRect = QRectF(left, 0.0, right - left, height); +} + +QRectF TemperatureDisplay::boundingRect () const +{ + return m_boundingRect; +} + +void TemperatureDisplay::paint(QPainter *painter) +{ + painter->translate(m_current.pos()); + m_current.paint(painter); + + painter->translate(m_upperBound.pos() - m_current.pos()); + m_upperBound.paint(painter); + + painter->translate(m_lowerBound.pos() - m_upperBound.pos()); + m_lowerBound.paint(painter); + + painter->translate(-m_lowerBound.pos()); + + painter->drawPixmap(m_linePos, m_line); +} + +// CityInfoDisplay + +CityInfoDisplay::CityInfoDisplay(QGraphicsItem *parent) + : QGraphicsItem(parent) + , m_brush(Qt::white) +{ + setCacheMode(ItemCoordinateCache); + m_temperature.setPos(0.0, 0.0); + + m_font.setFamily("Nokia Sans"); + m_font.setPixelSize(CITY_NAME_FONT_SIZE); + m_font.setStyleStrategy(QFont::PreferAntialias); + + m_pen.setColor(Qt::white); + m_pen.setBrush(Qt::white); + m_pen.setJoinStyle(Qt::RoundJoin); + + setCityName("unknown"); +} + +void CityInfoDisplay::updateBoundingRect() +{ + const qreal right = qMax(m_temperature.boundingRect().right(), -m_textPos.x()); + const qreal left = qMin(m_temperature.boundingRect().left(), m_textPos.x()); + m_boundingRect = QRectF(left, 0.0, right - left, m_nameRect.bottom()); +} + +void CityInfoDisplay::setTemperature(int lowerBound, int upperBound, int current) +{ + prepareGeometryChange(); + m_temperature.setTemperature(lowerBound, upperBound, current); + updateBoundingRect(); + update(); +} + +void CityInfoDisplay::setCityName(const QString &name) +{ + prepareGeometryChange(); + QFontMetrics metrics(m_font); + m_text = metrics.elidedText(name, Qt::ElideRight, MAX_CITY_NAME_WIDTH); + + int width = metrics.width(m_text); + int right = width >> 1; + qreal top = CITY_NAME_TOP; + m_textPos = QPointF(-right, top + metrics.height()); + m_nameRect = QRectF(-right, top, 2 * right, metrics.height() + metrics.descent()); + + updateBoundingRect(); + update(); +} + +QRectF CityInfoDisplay::boundingRect () const +{ + return m_boundingRect; +} + +void CityInfoDisplay::paint(QPainter *painter, const QStyleOptionGraphicsItem *opt, QWidget *widget) +{ + Q_UNUSED(opt); + Q_UNUSED(widget); + + painter->setPen(m_pen); + painter->setBrush(m_brush); + painter->setFont(m_font); + painter->drawText(m_textPos, m_text); + + painter->translate(m_temperature.pos()); + m_temperature.paint(painter); +} + +void CityInfoDisplay::mousePressEvent(QGraphicsSceneMouseEvent *event) +{ + if (m_nameRect.contains(event->pos())) { + emit nameClicked(); + } else + event->ignore(); +} + +int CityInfoDisplay::loadImages() +{ + PixmapLoader::load("minus_sign"); + PixmapLoader::load("centigrades"); + PixmapLoader::load("icon_min"); + PixmapLoader::load("icon_max"); + PixmapLoader::load("division_line"); + return 5; +} diff --git a/weather/src/cityinfodisplay.h b/weather/src/cityinfodisplay.h new file mode 100644 index 0000000..6b6dc3b --- /dev/null +++ b/weather/src/cityinfodisplay.h @@ -0,0 +1,163 @@ +/**************************************************************************** +** +** 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 CITYINFODISPLAY_H +#define CITYINFODISPLAY_H + +#include <QGraphicsItem> +#include <QGraphicsTextItem> +#include <QGraphicsSimpleTextItem> +#include <QGraphicsPixmapItem> +#include <QPen> +#include <QBrush> +#include <QFont> + +class CurrentTemperatureDisplay +{ +public: + CurrentTemperatureDisplay(); + + void setTemperature(int value); + + QPointF pos() const { return m_pos; } + void setPos(QPointF pos) { m_pos = pos; } + void setPos(qreal x, qreal y) { setPos(QPointF(x, y)); } + + QRectF boundingRect () const; + void paint(QPainter *painter); +private: + QPen m_pen; + QBrush m_brush; + QFont m_font; + QPixmap m_sign; + QPixmap m_unit; + QString m_text; + int m_value; + QRectF m_boundingRect; + QPointF m_pos; + + QPointF m_signPos; + QPointF m_numberPos; + QPointF m_unitPos; + +}; + +class TemperatureBoundDisplay +{ +public: + TemperatureBoundDisplay(bool lowerBound); + + bool lowerBound() const { return m_lowerBound; } + int value() const { return m_value; } + void setValue(int value); + + QPointF pos() const { return m_pos; } + void setPos(QPointF pos) { m_pos = pos; } + void setPos(qreal x, qreal y) { setPos(QPointF(x, y)); } + + QRectF boundingRect () const; + void paint(QPainter *painter); + +private: + QPen m_pen; + QBrush m_brush; + QFont m_font; + QPixmap m_icon; + + QString m_text; + int m_value; + const bool m_lowerBound; + QRectF m_boundingRect; + QPointF m_pos; +}; + +class TemperatureDisplay +{ +public: + TemperatureDisplay(); + + void setTemperature(int lowerBound, int upperBound, int current); + + QPointF pos() const { return m_pos; } + void setPos(QPointF pos) { m_pos = pos; } + void setPos(qreal x, qreal y) { setPos(QPointF(x, y)); } + + QRectF boundingRect () const; + void paint(QPainter *painter); + +private: + QPixmap m_line; + TemperatureBoundDisplay m_lowerBound; + TemperatureBoundDisplay m_upperBound; + CurrentTemperatureDisplay m_current; + + QRectF m_boundingRect; + QPointF m_linePos; + QPointF m_pos; + +}; + +class CityInfoDisplay : public QObject, public QGraphicsItem +{ + Q_OBJECT + Q_INTERFACES(QGraphicsItem) +public: + CityInfoDisplay(QGraphicsItem *parent = 0); + + static int loadImages(); + + void setTemperature(int lowerBound, int upperBound, int current); + void setCityName(const QString &name); + + QRectF boundingRect () const; + void paint(QPainter *painter, const QStyleOptionGraphicsItem *option, QWidget *widget = 0); + +signals: + void nameClicked(); + +protected: + void mousePressEvent(QGraphicsSceneMouseEvent *event); + +private: + QRectF m_boundingRect; + TemperatureDisplay m_temperature; + QPen m_pen; + QBrush m_brush; + QFont m_font; + QString m_text; + QPointF m_textPos; + QRectF m_nameRect; + + void updateBoundingRect(); + +}; + +#endif // CITYINFODISPLAY_H diff --git a/weather/src/citylist.cpp b/weather/src/citylist.cpp new file mode 100644 index 0000000..d3ac7fd --- /dev/null +++ b/weather/src/citylist.cpp @@ -0,0 +1,465 @@ +#include "citylist.h" +#include "settings.h" +#include "pixmaploader.h" +#include <QPainter> +#include <cmath> +#include <QParallelAnimationGroup> +#include <QPropertyAnimation> + +#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 = 6; +static const int minListCount = 3; + +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 QT_ARCH_ARM +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)); + + m_content->setZValue(0.0); + m_scroll->setZValue(1.0); +} + +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); +} + +// CityListText + +CityListText::CityListText(const QString &text, QGraphicsItem *parent) + : QGraphicsItem(parent) + , m_painter(CITY_NAME_FONT_SIZE, Qt::white, text) +{ + setCacheMode(ItemCoordinateCache); + m_painter.setPos(0.0, 0.0); + m_painter.setMaxWidth(ITEM_BUTTON_LEFT - TEXT_LEFT); +} + +QRectF CityListText::boundingRect() const +{ + return QRectF(0.0, 0.0, m_painter.width(), m_painter.height()); +} + +void CityListText::paint(QPainter *painter, const QStyleOptionGraphicsItem *opt, QWidget *widget) +{ + Q_UNUSED(opt); + Q_UNUSED(widget); + m_painter.paint(painter); +} + +// CityListItem + +CityListItem::CityListItem(const ForecastData &data, CityContentList *list, bool deleteVisible) + : 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(new CityListText(m_data.cityName(), this)) +{ + m_delete->setPos(ITEM_BUTTON_LEFT, getCenterVerticalPos(m_delete)); + m_delete->setVisible(deleteVisible); + m_delete->setOpacity(deleteVisible ? 1.0 : 0.0); + m_check->setPos(ITEM_CHECK_LEFT, getCenterVerticalPos(m_check)); + m_check->hide(); + m_text->setPos(TEXT_LEFT, getCenterVerticalPos(m_text)); + m_text->setCacheMode(ItemCoordinateCache); + + connect(m_delete, SIGNAL(clicked()), this, SLOT(removeFromList())); +} + +void CityListItem::select(bool selected) +{ + m_background->setPixmap(selected ? SELECTED_ITEM_BACKGROUND : ITEM_BACKGROUND); + m_check->setVisible(selected); +} + +void CityListItem::removeFromList() +{ + if (!m_list->busy()) + m_list->removeItem(this); +} + +qreal CityListItem::contentHeight() const +{ + return m_height; +} + +QAbstractAnimation *CityListItem::getButtonAnimation(bool hide) +{ + return getFadeAnimation(m_delete, hide, 150); +} + +QAbstractAnimation *CityListItem::getShowAnimation() +{ + return getFadeAnimation(this, false, 150); +} + +QAbstractAnimation *CityListItem::getHideAnimation() +{ + return getFadeAnimation(this, true, 150); +} + +template<class T>QAbstractAnimation *CityListItem::getFadeAnimation(T *target, bool hide, int msecs) +{ + target->setOpacity(hide ? 1.0 : 0.0); + target->show(); + QPropertyAnimation* lResult = new QPropertyAnimation(target, "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()), target, SLOT(doHide())); + return lResult; +} + +// CityContentList + +CityContentList::CityContentList(QList<ForecastData> &contentList, QObject *holder) + : ContentList(0) + , m_holder(holder) + , m_contentList(contentList) +{ + QList<ContentListItem*> items; + foreach (const ForecastData &data, m_contentList) + items.append(new CityListItem(data, this, m_contentList.count() > minListCount)); + appendItems(items); +} + +void CityContentList::addForecast(const ForecastData &data) +{ + m_contentList.append(data); + addItem(new CityListItem(data, this, m_contentList.count() > minListCount)); +} + +void CityContentList::select(const QString &selected) +{ + for (int i = 0; i < itemCount(); ++i) { + CityListItem *item = static_cast<CityListItem*>(getItem(i)); + if (item) + item->select(item->data().key() == selected); + } +} + +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)); + } + + if (parentItem() && itemCount() >= maxVisibleItems) { + qreal top = parentItem()->boundingRect().height() - boundingRect().height() - height; + list.append(getMoveAnimation(this, top - pos().y())); + + } + + if (itemCount() == minListCount) { + for (int i = 0; i < minListCount; ++i) { + CityListItem *item = static_cast<CityListItem*>(getItem(i)); + if (item) + list.append(item->getButtonAnimation(false)); + } + } + + 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)); + + qreal newHeight = boundingRect().height() - getItem(idx)->contentHeight(); + + if (parentItem()) { + if (newHeight < parentItem()->boundingRect().height()) { + if (pos().y() != 0) + list.append(getMoveAnimation(this, -pos().y())); + } else { + if (pos().y() + newHeight < parentItem()->boundingRect().bottom()) { + qreal offset = parentItem()->boundingRect().bottom() - pos().y() - newHeight; + list.append(getMoveAnimation(this, offset)); + } + } + } + + if (itemCount() == minListCount + 1) { + for (int i = 0; i <= minListCount; ++i) { + if (i != idx) { + CityListItem *item = static_cast<CityListItem*>(getItem(i)); + if (item) + list.append(item->getButtonAnimation(true)); + + } + } + } + + m_contentList.removeAt(idx); + + + 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(QList<ForecastData> &contentList, 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(contentList, this)) + , m_scrollBox(new CityListScrollBox(m_list, this)) +{ + m_top->setPos(-m_top->boundingRect().left(), -m_top->boundingRect().top()); + m_scrollBox->setRect(m_paintRect); + m_list->setWidth(m_paintRect.width()); +} + +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/src/citylist.h b/weather/src/citylist.h new file mode 100644 index 0000000..3d93806 --- /dev/null +++ b/weather/src/citylist.h @@ -0,0 +1,169 @@ +#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" +#include "painttextitem.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(); +}; + +// CityListText + +class CityListText : public QGraphicsItem +{ +public: + CityListText(const QString &text, QGraphicsItem *parent = 0); + QRectF boundingRect() const; + void paint(QPainter *painter, const QStyleOptionGraphicsItem *option, QWidget *widget = 0); + +private: + TextPainter m_painter; + +}; + +// CityListItem + +class CityContentList; + +class CityListItem : public ContentListItem +{ + Q_OBJECT + Q_PROPERTY(qreal opacity READ opacity WRITE setOpacity); +public: + CityListItem(const ForecastData &data, CityContentList *list, bool deleteVisible); + void select(bool selected); + + const ForecastData &data() const { return m_data; } + + qreal contentHeight() const; + QAbstractAnimation *getShowAnimation(); + QAbstractAnimation *getHideAnimation(); + + QAbstractAnimation *getButtonAnimation(bool hide); + +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; + CityListText * const m_text; + + template<class T> static QAbstractAnimation *getFadeAnimation(T *target, bool hide, int msecs); + +private slots: + void doHide() { hide(); } + +}; + +// CityContentList + +class CityContentList : public ContentList +{ + Q_OBJECT + Q_PROPERTY(qreal top READ getTop WRITE setTop); +public: + CityContentList(QList<ForecastData> &contentList, QObject *holder); + void addForecast(const ForecastData &data); + void select(const QString &selected); + +protected: + QAbstractAnimation *getInsertAnimation(int idx, qreal height); + QAbstractAnimation *getRemoveAnimation(int idx); + +private: + QObject * const m_holder; + QList<ForecastData> &m_contentList; + + QAbstractAnimation *createCompoundAnimation(QList<QAbstractAnimation*> list); + QAbstractAnimation *getItemMoveAnimation(int idx, qreal offset); + QAbstractAnimation *getMoveAnimation(QObject *object, qreal offset); + + qreal getTop() { return pos().y(); } + void setTop(qreal top) { setPos(pos().x(), top); } + +}; + +// CityList + +class CityList : public QObject, public QGraphicsItem +{ + Q_OBJECT + Q_PROPERTY(qreal top READ getTop WRITE setTop); + Q_INTERFACES(QGraphicsItem); +public: + CityList(QList<ForecastData> &contentList, QGraphicsItem *parent = 0); + void select(const QString &selected) { m_list->select(selected); } + + 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/src/citymanager.cpp b/weather/src/citymanager.cpp new file mode 100644 index 0000000..665c3b7 --- /dev/null +++ b/weather/src/citymanager.cpp @@ -0,0 +1,146 @@ +/**************************************************************************** +** +** 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 "citymanager.h" +#include "settings.h" + +#include <QGraphicsSceneMouseEvent> +#include <QPropertyAnimation> + +static inline qreal getBottomVerticalPos(qreal &bottom, QGraphicsItem *item) +{ + const qreal result = bottom - item->boundingRect().bottom(); + bottom -= item->boundingRect().height(); + return result; +} + +// CityManagerContent + +CityManagerContent::CityManagerContent(QList<ForecastData> &contentList, QGraphicsItem *parent) + : QGraphicsItem(parent) + , m_boundingRect(QPointF(0.0, 0.0), Settings::windowSize()) +{ + setFlag(ItemHasNoContents); + + qreal bottom = m_boundingRect.bottom(); + m_addTool = new AddCityTool(contentList, this); + m_addTool->setPos(0.0, getBottomVerticalPos(bottom, m_addTool)); + m_addTool->setZValue(10.0); + + m_list = new CityList(contentList, this); + m_list->setPos(0.0, getBottomVerticalPos(bottom, m_list) + m_list->initialTop() + 1); + m_list->setZValue(0.0); + + connect(m_addTool, SIGNAL(newForecast(ForecastData)), + this, SLOT(forecastSelected(ForecastData))); + +} + +void CityManagerContent::forecastSelected(ForecastData data) +{ + m_list->addForecast(data); +} + +QRectF CityManagerContent::boundingRect () const +{ + return m_boundingRect; +} + +void CityManagerContent::paint(QPainter *painter, + const QStyleOptionGraphicsItem *opt, QWidget *widget) +{ + Q_UNUSED(painter); + Q_UNUSED(opt); + Q_UNUSED(widget); +} + +qreal CityManagerContent::getHiddenTop() +{ + return m_boundingRect.height() - m_list->pos().y() - 5.0; +} + +// CityManager + +CityManager::CityManager(QList<ForecastData> contentList, QGraphicsItem *parent) + : QGraphicsItem(parent) + , m_boundingRect(QPointF(0.0, 0.0), Settings::windowSize()) + , m_contentList(contentList) + , m_visible(false) +{ + m_content = new CityManagerContent(m_contentList, this); + m_content->setPos(0.0, m_content->getHiddenTop()); +} + +QRectF CityManager::boundingRect () const +{ + return m_boundingRect; +} + +void CityManager::paint(QPainter *painter, const QStyleOptionGraphicsItem *opt, QWidget *widget) +{ + Q_UNUSED(painter); + Q_UNUSED(opt); + Q_UNUSED(widget); +} + +void CityManager::mousePressEvent(QGraphicsSceneMouseEvent *event) +{ + event->accept(); + startAnimation(false); +} + +void CityManager::showManager(const QString &selected) +{ + m_content->select(selected); + startAnimation(true); +} + +void CityManager::startAnimation(bool show) +{ + if (show == m_visible) + return; + + m_visible = show; + + if (m_animation) + m_animation->stop(); + + QPropertyAnimation* animation = new QPropertyAnimation(m_content, "top"); + + animation->setEasingCurve(QEasingCurve::OutExpo); + animation->setEndValue(show ? 0.0 : m_content->getHiddenTop()); + animation->setDuration(500); + m_animation = animation; + if (!show) + connect(animation, SIGNAL(finished()), this, SIGNAL(terminated())); + m_animation->start(QAbstractAnimation::DeleteWhenStopped); + +} diff --git a/weather/src/citymanager.h b/weather/src/citymanager.h new file mode 100644 index 0000000..3b80393 --- /dev/null +++ b/weather/src/citymanager.h @@ -0,0 +1,97 @@ +/**************************************************************************** +** +** 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 CITYMANAGER_H +#define CITYMANAGER_H + +#include <QGraphicsItem> +#include <QGraphicsPixmapItem> + +#include "addcitytool.h" +#include "citylist.h" + +class CityManagerContent : public QObject, public QGraphicsItem +{ + Q_OBJECT + Q_INTERFACES(QGraphicsItem) + Q_PROPERTY(qreal top READ getTop WRITE setTop); +public: + CityManagerContent(QList<ForecastData> &contentList, QGraphicsItem *parent = 0); + void select(const QString &selected) { m_list->select(selected); } + + QRectF boundingRect () const; + void paint(QPainter *painter, const QStyleOptionGraphicsItem *option, QWidget *widget = 0); + + qreal getHiddenTop(); + +private: + const QRectF m_boundingRect; + AddCityTool *m_addTool; + CityList *m_list; + + qreal getTop() { return pos().y(); } + void setTop(qreal top) { setPos(pos().x(), top); } + +private slots: + void forecastSelected(ForecastData data); + +}; + +class CityManager : public QObject, public QGraphicsItem +{ + Q_OBJECT + Q_INTERFACES(QGraphicsItem) +public: + CityManager(QList<ForecastData> contentList, QGraphicsItem *parent = 0); + void showManager(const QString &selected); + + QRectF boundingRect () const; + void paint(QPainter *painter, const QStyleOptionGraphicsItem *option, QWidget *widget = 0); + + QList<ForecastData> forecastList() const { return m_contentList; } + +protected: + void mousePressEvent(QGraphicsSceneMouseEvent *event); + +signals: + void terminated(); + +private: + const QRectF m_boundingRect; + CityManagerContent *m_content; + QList<ForecastData> m_contentList; + QPointer<QAbstractAnimation> m_animation; + bool m_visible; + + void startAnimation(bool show); +}; + +#endif // CITYMANAGER_H diff --git a/weather/src/contentlist.cpp b/weather/src/contentlist.cpp new file mode 100644 index 0000000..8b054aa --- /dev/null +++ b/weather/src/contentlist.cpp @@ -0,0 +1,441 @@ +/**************************************************************************** +** +** 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)) + , m_geometryReady(false) +{ + setFlag(QGraphicsItem::ItemHasNoContents, true); + setFlag(QGraphicsItem::ItemSendsGeometryChanges, true); +} + +QRectF ContentListItem::boundingRect() const +{ + if (!m_geometryReady) + return QRectF(0.0, 0.0, 0.0, contentHeight()); + return m_geometry; +} + +void ContentListItem::updateGeometry() +{ + m_geometryReady = parentItem() != 0; + qreal width = parentItem() ? parentItem()->boundingRect().width() - pos().x() : 0.0; + QRectF geometry(0.0, 0.0, width, 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/src/contentlist.h b/weather/src/contentlist.h new file mode 100644 index 0000000..8f37c7f --- /dev/null +++ b/weather/src/contentlist.h @@ -0,0 +1,261 @@ +/**************************************************************************** +** +** 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; + bool m_geometryReady; + 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/src/demoforecastsource.cpp b/weather/src/demoforecastsource.cpp new file mode 100644 index 0000000..292b6ea --- /dev/null +++ b/weather/src/demoforecastsource.cpp @@ -0,0 +1,142 @@ +#include "demoforecastsource.h" +#include "forecast.h" + +#include <QTimer> +#include <QFile> +#include <QDebug> + +struct CityInfo +{ + const char * name; + const int lower; + const int temp; + const int upper; + const bool night; +}; + +static const CityInfo cityNames[Forecast::UnknownForecast] = { + {"Araxa", -7, -4, -1, true}, // MostlyCloudy + {"Belo Horizonte", 18, 22, 27, false}, // Cloudy + {"Recife", 25, 31, 34, true}, // MostlySunny + {"Ipojuca", 22, 29, 36, false}, // PartlyCloudy + {"Fortaleza", 31, 34, 36, true}, // Sunny + {"Sao Paulo", -1, 10, 12, false}, // Flurries + {"Uberlandia", -26, -23, -20, false}, // Fog + {"Governador Valadares", 3, 17, 18, true}, // Haze + {"Brasilia", 22, 25, 30, false}, // Sand + {"Florianopolis", 0, 8, 10, true}, // Dust + {"Gravata", -10, -2, 3, false}, // Icy + {"Maceio", 22, 35, 38, true}, // Sleet + {"Porto Alegre", -8, -6, -4, true}, // ChanceOfSleet + {"Batatais", -20, -12, -10, false}, // Snow + {"Ribeirao Preto", -20, -17, 0, true}, // ChanceOfSnow + {"Campinas", 1, 4, 10, false}, // Mist + {"Campo belo", 12, 19, 21, true}, // Rain + {"Sertaozinho", 10, 15, 17, true}, // ChanceOfRain + {"Pirassununga", -15, -12, 2, false}, // Storm + {"Goiania", 7, 13, 18, true}, // ChanceOfStorm + {"Manaus", 35, 41, 44, false}, // Thunderstorm + {"Belem", -14, -13, -5, true} // ChanceOfThunderstorm +}; + +DemoForecastSource::DemoForecastSource(int waitTime, QObject *parent) + : ForecastSource(parent) + , m_waitTime(waitTime) + , m_idSeq(0) +{ + createContentList(); +} + + +int DemoForecastSource::findByLocId(const QString &locId) +{ + if (!locId.isEmpty()) + for (int i = 0; i < m_requests.count(); ++i) + if (m_requests[i].m_locId == locId) + return i; + return -1; +} + +int DemoForecastSource::findByQuery(const QString &query) +{ + if (!query.isEmpty()) + for (int i = 0; i < m_requests.count(); ++i) + if (m_requests[i].m_cityQuery == query) + return i; + return -1; +} + +QString DemoForecastSource::findCity(const QString &name) +{ + QString city = name.toUpper(); + for (int i = 0; i < m_list.count(); ++i) { + if (city == m_list[i].cityName().toUpper()) + return m_list[i].key(); + } + return QString(); +} + +int DemoForecastSource::getForecast(const QString &key, bool locationId) +{ + + int idx = locationId ? findByLocId(key) : findByQuery(key); + if (idx >= 0) + return m_requests[idx].m_reqId; + + QString locId = locationId ? key : findCity(key); + + Request request; + request.m_locId = locId; + request.m_cityQuery = key; + request.m_reqId = getId(); + m_requests.append(request); + if (m_waitTime > 0) + QTimer::singleShot(m_waitTime, this, SLOT(sendResponse())); + else sendResponse(); + return request.m_reqId; +} + +void DemoForecastSource::createContentList() +{ + QFile file(":fake_content.xml"); + if (!file.open(QIODevice::ReadOnly)) { + qWarning() << "cannot open fake content file."; + return; + } + QDomDocument doc; + QString msg; + if (!doc.setContent(&file, &msg)) { + qWarning() << "fake content parse error: " << msg; + return; + } + if (doc.documentElement().childNodes().count() == 0) { + qWarning() << "fake content is empty."; + return; + } + QDomElement root = doc.documentElement(); + for (int i = 0; i < root.childNodes().count(); ++i) { + if (root.childNodes().at(i).isElement()) { + QDomElement element = root.childNodes().at(i).toElement(); + QString id = element.attribute("locId"); + if (!id.isEmpty() && element.childNodes().count() == 1 && + element.childNodes().at(0).isElement()) { + QDomElement child = element.childNodes().at(0).toElement(); + m_list.append(ForecastData(new YahooWeatherResponse(id, child))); + } + } + } +} + +void DemoForecastSource::sendResponse() +{ + if (!m_requests.isEmpty()) { + Request request = m_requests.takeFirst(); + for (int i = 0; i < m_list.count(); ++i) { + if (m_list[i].key() == request.m_locId) { + emit forecastReceived(request.m_reqId, m_list[i]); + return; + } + } + emit forecastReceived(request.m_reqId, ForecastData(0)); + } +} diff --git a/weather/src/demoforecastsource.h b/weather/src/demoforecastsource.h new file mode 100644 index 0000000..dbfd715 --- /dev/null +++ b/weather/src/demoforecastsource.h @@ -0,0 +1,44 @@ +#ifndef DEMOFORECASTSOURCE_H +#define DEMOFORECASTSOURCE_H + +#include "forecastsource.h" +#include "yahooweatherresponse.h" + +#include <QList> + +class DemoForecastSource : public ForecastSource +{ + Q_OBJECT +public: + DemoForecastSource(int waitTime, QObject *parent = 0); + int getForecast(const QString &key, bool locationId); + +private: + + class Request + { + public: + int m_reqId; + QString m_locId; + QString m_cityQuery; + }; + + const int m_waitTime; + QList<Request> m_requests; + QList<ForecastData> m_list; + int m_idSeq; + + void createContentList(); + QString findCity(const QString &name); + + int getId() { return ++m_idSeq; } + + int findByLocId(const QString &locId); + int findByQuery(const QString &query); + +private slots: + void sendResponse(); + +}; + +#endif // DEMOFORECASTSOURCE_H diff --git a/weather/src/fakecontentscreen.cpp b/weather/src/fakecontentscreen.cpp new file mode 100644 index 0000000..493df9c --- /dev/null +++ b/weather/src/fakecontentscreen.cpp @@ -0,0 +1,104 @@ +#include "fakecontentscreen.h" +#include "pixmaploader.h" +#include "settings.h" +#include "painttextitem.h" + +#include <QPainter> +#include <QDebug> +#include <QCoreApplication> +#include <QGraphicsSceneMouseEvent> + +#define SCREEN_FONT_SIZE (Settings::scaleHeight(30.0)) +#define BUTTON_FONT_SIZE (Settings::scaleHeight(40.0)) + +#define LEFT_BUTTON_NAME "button_softkey_right" +#define RIGHT_BUTTON_NAME "button_softkey_left" + +#define LEFT_PIXMAP (PixmapLoader::getPic(LEFT_BUTTON_NAME)) +#define RIGHT_PIXMAP (PixmapLoader::getPic(RIGHT_BUTTON_NAME)) + +FakeContentScreen::FakeContentScreen(QGraphicsItem *parent) + : QGraphicsItem(parent) + , m_boundingRect(QPointF(0.0, 0.0), Settings::windowSize()) + , m_left(LEFT_PIXMAP) + , m_right(RIGHT_PIXMAP) +{ +} + +QRectF FakeContentScreen::boundingRect() const +{ + return m_boundingRect; +} + +void FakeContentScreen::paint(QPainter *painter, + const QStyleOptionGraphicsItem *opt, QWidget *widget) +{ + Q_UNUSED(opt); + Q_UNUSED(widget); + + static TextPainter text1(SCREEN_FONT_SIZE, QColor(255, 255, 255, 255), "You have no internet"); + static TextPainter text2(SCREEN_FONT_SIZE, QColor(255, 255, 255, 255), + "connection. Do you want to"); + static TextPainter text3(SCREEN_FONT_SIZE, QColor(255, 255, 255, 255), + "continue to the off-line"); + static TextPainter text4(SCREEN_FONT_SIZE, QColor(255, 255, 255, 255), "weather demo?"); + + static TextPainter left(BUTTON_FONT_SIZE, QColor(255, 255, 255, 255), "YES"); + static TextPainter right(BUTTON_FONT_SIZE, QColor(255, 255, 255, 255), "EXIT"); + + static const int buttonTop = boundingRect().height() - m_left.height(); + static const int buttonLeft = boundingRect().width() - m_right.width(); + + static bool firstTime = true; + + if (firstTime) { + firstTime = false; + const qreal top = (boundingRect().height() - 4 * text1.height() - m_left.height()) / 2; + TextPainter::locateAtCenter(&text1, 0.0, top, boundingRect().width()); + TextPainter::locateAtCenter(&text2, 0.0, top + 1 * text1.height(), boundingRect().width()); + TextPainter::locateAtCenter(&text3, 0.0, top + 2 * text1.height(), boundingRect().width()); + TextPainter::locateAtCenter(&text4, 0.0, top + 3 * text1.height(), boundingRect().width()); + + const qreal buttonTextTop = (m_left.height() - left.height()) / 2 + buttonTop; + left.setPos(30, buttonTextTop); + right.setPos(boundingRect().width() - right.width() - 30, buttonTextTop); + + } + + painter->fillRect(boundingRect(), QColor(7, 18, 23, 255)); + + text1.paint(painter); + text2.paint(painter); + text3.paint(painter); + text4.paint(painter); + + painter->drawPixmap(0, buttonTop, m_left); + painter->drawPixmap(buttonLeft, buttonTop, m_right); + + left.paint(painter); + right.paint(painter); + +} + +int FakeContentScreen::loadImages() +{ + PixmapLoader::load(LEFT_BUTTON_NAME); + PixmapLoader::load(RIGHT_BUTTON_NAME); + return 2; +} + +void FakeContentScreen::mousePressEvent(QGraphicsSceneMouseEvent *event) +{ + static QRectF left(0.0, boundingRect().height() - m_left.height(), + m_left.width(), m_left.height()); + static QRectF right(boundingRect().width() - m_right.width(), + boundingRect().height() - m_left.height(), + m_right.width(), m_right.height()); + + + if (left.contains(event->pos())) + emit userAccepted(); + else + QCoreApplication::instance()->quit(); + +} diff --git a/weather/src/fakecontentscreen.h b/weather/src/fakecontentscreen.h new file mode 100644 index 0000000..09c788a --- /dev/null +++ b/weather/src/fakecontentscreen.h @@ -0,0 +1,30 @@ +#ifndef FAKECONTENTSCREEN_H +#define FAKECONTENTSCREEN_H + +#include <QObject> +#include <QGraphicsItem> + +class FakeContentScreen : public QObject, public QGraphicsItem +{ + Q_OBJECT + Q_INTERFACES(QGraphicsItem) +public: + FakeContentScreen(QGraphicsItem *parent = 0); + static int loadImages(); + + QRectF boundingRect() const; + void paint(QPainter *painter, const QStyleOptionGraphicsItem *option, QWidget *widget = 0); + +protected: + void mousePressEvent(QGraphicsSceneMouseEvent *event); + +private: + const QRectF m_boundingRect; + QPixmap m_left; + QPixmap m_right; + +signals: + void userAccepted(); +}; + +#endif // FAKECONTENTSCREEN_H diff --git a/weather/src/forecast.h b/weather/src/forecast.h new file mode 100644 index 0000000..f6fb6a8 --- /dev/null +++ b/weather/src/forecast.h @@ -0,0 +1,72 @@ +/**************************************************************************** +** +** 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 FORECAST_H +#define FORECAST_H + +namespace Forecast +{ + enum ForecastType + { + MostlyCloudy = 0, + Cloudy, + MostlySunny, + PartlyCloudy, + Sunny, + Flurries, + Fog, + Haze, + Sand, + Dust, + Icy, + Sleet, + ChanceOfSleet, + Snow, + ChanceOfSnow, + Mist, + Rain, + ChanceOfRain, + Storm, + ChanceOfStorm, + Thunderstorm, + ChanceOfThunderstorm, + UnknownForecast + }; + + enum Effect + { + FogEffect = 0, + HazeEffect, + NoEffect + }; +} + +#endif // FORECAST_H diff --git a/weather/src/forecastdata.cpp b/weather/src/forecastdata.cpp new file mode 100644 index 0000000..45e2567 --- /dev/null +++ b/weather/src/forecastdata.cpp @@ -0,0 +1,78 @@ +#include "forecastdata.h" + +struct ConditionTypeData +{ + const int id; + Forecast::ForecastType type; +}; + +static const int ContitionTypeCount = 47; +static const ConditionTypeData ContitionType[ContitionTypeCount] = { + {1, Forecast::Storm}, + {2, Forecast::Thunderstorm}, + {3, Forecast::Thunderstorm}, + {4, Forecast::Thunderstorm}, + {5, Forecast::Sleet}, + {6, Forecast::Rain}, + {7, Forecast::Sleet}, + {8, Forecast::Mist}, + {9, Forecast::Mist}, + {10, Forecast::Rain}, + {11, Forecast::Rain}, + {12, Forecast::Rain}, + {13, Forecast::Flurries}, + {14, Forecast::Flurries}, + {15, Forecast::Snow}, + {16, Forecast::Snow}, + {17, Forecast::Snow}, + {18, Forecast::Sleet}, + {19, Forecast::Haze}, + {20, Forecast::Fog}, + {21, Forecast::Haze}, + {22, Forecast::Fog}, + {23, Forecast::Cloudy}, + {24, Forecast::Cloudy}, + {25, Forecast::Cloudy}, + {26, Forecast::Cloudy}, + {27, Forecast::MostlyCloudy}, + {28, Forecast::MostlyCloudy}, + {29, Forecast::PartlyCloudy}, + {30, Forecast::PartlyCloudy}, + {31, Forecast::Sunny}, + {32, Forecast::Sunny}, + {33, Forecast::MostlySunny}, + {34, Forecast::MostlySunny}, + {35, Forecast::Sleet}, + {36, Forecast::Sunny}, + {37, Forecast::Thunderstorm}, + {38, Forecast::Thunderstorm}, + {39, Forecast::Thunderstorm}, + {40, Forecast::Storm}, + {41, Forecast::Snow}, + {42, Forecast::Sleet}, + {43, Forecast::Snow}, + {44, Forecast::PartlyCloudy}, + {45, Forecast::Thunderstorm}, + {46, Forecast::Sleet}, + {47, Forecast::Thunderstorm} +}; + + +Forecast::ForecastType ForecastData::type() const +{ + if (m_data) { + for (int i = 0; i < ContitionTypeCount; ++i) + if (ContitionType[i].id == m_data->condition().code()) + return ContitionType[i].type; + } + return Forecast::UnknownForecast; +} + +bool ForecastData::night() const +{ + if (!m_data) + return false; + return m_data->condition().date().time() < m_data->astronomy().sunrise() + || m_data->condition().date().time() > m_data->astronomy().sunset(); +} + diff --git a/weather/src/forecastdata.h b/weather/src/forecastdata.h new file mode 100644 index 0000000..bf5b9cd --- /dev/null +++ b/weather/src/forecastdata.h @@ -0,0 +1,44 @@ +#ifndef FORECASTDATA_H +#define FORECASTDATA_H + +#include <QString> +#include "forecast.h" +#include "yahooweatherresponse.h" + +#include <QSharedPointer> + +class YahooWeatherResponse; + +class ForecastData +{ +public: + ForecastData() : m_error(false), m_data(0) {} + ForecastData(YahooWeatherResponse *data) : m_error(!data), m_data(data) {} + + Forecast::ForecastType type() const; + bool night() const; + bool error() const { return m_error; } + QString key() const { return m_data ? m_data->locationCode() : QString(); } + QString cityName() const { return m_data ? m_data->location().city() : QString(); } + int lower() const { return m_data ? m_data->forecast(0).low() : 0; } + int current() const { return m_data ? m_data->condition().temperature() : 0; } + int upper() const { return m_data ? m_data->forecast(0).high() : 0; } + bool isNull() const { return !m_error && !m_data; } + + inline bool operator==(const ForecastData &other); + bool operator!=(const ForecastData &other) { return !operator==(other); } + +private: + bool m_error; + + QSharedPointer<YahooWeatherResponse> m_data; +}; + +bool ForecastData::operator==(const ForecastData &other) +{ + return type() == other.type() && night() == other.night() && error() == other.error() + && key() == other.key() && cityName() == other.cityName() && lower() == other.lower() + && current() == other.current() && upper() == other.upper(); +} + +#endif // FORECASTDATA_H diff --git a/weather/src/forecasthungitem.cpp b/weather/src/forecasthungitem.cpp new file mode 100644 index 0000000..428b83c --- /dev/null +++ b/weather/src/forecasthungitem.cpp @@ -0,0 +1,159 @@ +/**************************************************************************** +** +** 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 "forecasthungitem.h" +#include "settings.h" +#include "pixmaploader.h" + +#include <QPropertyAnimation> + +#define LINE_NAME_SUFFIX "_line" + +typedef struct +{ + const char * const prefix; + const qreal x; + const qreal y; + + QString name() const { return prefix; } + QString lineName() const { return name() + LINE_NAME_SUFFIX; } + + qreal scaledX() const { return Settings::scaleWidth(x); } + qreal scaledY() const { return Settings::scaleHeight(y); } + + QPixmap pic() const { return PixmapLoader::getPic(name()); } + QPixmap linePic() const { return PixmapLoader::getPic(lineName()); } + +private: + +} HungObjectData; + +static HungObjectData HungItemsData[ForecastHungItem::TypeCount] = { + {"cloud_1", 160.0, 3.0}, // Cloud1 + {"cloud_2", 131.0, 6.0}, // Cloud2 + {"cloud_3", 102.0, 11.0}, // Cloud3 + {"cloud_rain_1", 160.0, 3.0}, // CloudRain1 + {"cloud_rain_2", 131.0, 6.0}, // CloudRain2 + {"cloud_rain_3", 102.0, 11.0}, // CloudRain3 + {"cloud_storm_1", 160.0, 3.0}, // CloudStorm1 + {"cloud_storm_2", 131.0, 6.0}, // CloudStorm2 + {"cloud_storm_3", 102.0, 11.0}, // CloudStorm3 + {"cloud_tstorm_1", 160.0, 3.0}, // CloudTStorm1 + {"cloud_tstorm_2", 131.0, 6.0}, // CloudTStorm2 + {"sun", 198.0, 125.0}, // Sun + {"cold_sun", 121.5, 65.0}, // ColdSun + {"moon", 97.5, 18.0} // Moon +}; + +// ForecastHungItem + +ForecastHungItem::ForecastHungItem(ItemType type, QGraphicsItem *parent) + : QGraphicsItem(parent) + , m_type(type) + , m_targetPicTop(0) +{ + setFlag(QGraphicsItem::ItemHasNoContents, true); + + HungObjectData &data = HungItemsData[m_type]; + + m_pic = new QGraphicsPixmapItem(data.pic(), this); + m_line = new QGraphicsPixmapItem(data.linePic(), m_pic); + + m_pic->setPos(0.0, 0.0); + + m_line->setPos(data.scaledX(), data.scaledY() - m_line->boundingRect().bottom()); + reset(); +} + +int ForecastHungItem::loadImages() +{ + int result = 0; + for (int i = 0; i < ForecastHungItem::TypeCount; ++i) { + PixmapLoader::load(HungItemsData[i].name()); + PixmapLoader::load(HungItemsData[i].lineName()); + result += 2; + } + return result; +} + +qreal ForecastHungItem::lineLenght() const +{ + return m_targetPicTop + HungItemsData[m_type].scaledY(); +} + +void ForecastHungItem::setLineLenght(qreal lenght) +{ + m_targetPicTop = lenght - HungItemsData[m_type].scaledY(); +} + +void ForecastHungItem::setLinePos(qreal linePos) +{ + setPos(linePos - HungItemsData[m_type].scaledX(), 0.0); +} + +QPointF ForecastHungItem::picPos() const +{ + return QPointF(pos().x(), pos().y() + m_targetPicTop); +} + +void ForecastHungItem::setPicPos(QPointF picPos) +{ + setPos(picPos.x(), 0.0); + m_targetPicTop = picPos.y(); +} + +void ForecastHungItem::reset() +{ + setPicTop(-m_pic->boundingRect().height()); +} + +QRectF ForecastHungItem::boundingRect () const +{ + return QRectF(0.0, 0.0, m_pic->boundingRect().width(), + m_targetPicTop + m_pic->boundingRect().height()); +} + +void ForecastHungItem::paint(QPainter *painter, const QStyleOptionGraphicsItem *opt, QWidget *widget) +{ + Q_UNUSED(painter); + Q_UNUSED(opt); + Q_UNUSED(widget); +} + +QAbstractAnimation *ForecastHungItem::getAnimation() +{ + QPropertyAnimation *result = new QPropertyAnimation(this, "picTop"); + result->setStartValue(-m_pic->boundingRect().height()); + result->setEndValue(m_targetPicTop); + result->setEasingCurve(QEasingCurve::OutBack); + result->setDuration(500); + return result; +} diff --git a/weather/src/forecasthungitem.h b/weather/src/forecasthungitem.h new file mode 100644 index 0000000..bb86552 --- /dev/null +++ b/weather/src/forecasthungitem.h @@ -0,0 +1,92 @@ +/**************************************************************************** +** +** 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 FORECASTHUNGITEM_H +#define FORECASTHUNGITEM_H + +#include <QObject> +#include <QGraphicsItem> +#include <QAbstractAnimation> + +class ForecastHungItem : public QObject, public QGraphicsItem +{ + Q_OBJECT + Q_INTERFACES(QGraphicsItem) + Q_PROPERTY(qreal picTop READ picTop WRITE setPicTop) +public: + enum ItemType { + Cloud1 = 0, + Cloud2, + Cloud3, + CloudRain1, + CloudRain2, + CloudRain3, + CloudStorm1, + CloudStorm2, + CloudStorm3, + CloudTStorm1, + CloudTStorm2, + Sun, + ColdSun, + Moon, + TypeCount + }; + + static int loadImages(); + + ForecastHungItem(ItemType type, QGraphicsItem *parent = 0); + QAbstractAnimation *getAnimation(); + + qreal lineLenght() const; + void setLineLenght(qreal lenght); + void setLinePos(qreal linePos); + + QPointF picPos() const; + void setPicPos(QPointF pos); + void setPicPos(qreal x, qreal y) { setPicPos(QPointF(x, y)); } + + void reset(); + + QRectF boundingRect () const; + void paint(QPainter *painter, const QStyleOptionGraphicsItem *option, QWidget *widget = 0); + +private: + QGraphicsPixmapItem *m_pic; + QGraphicsPixmapItem *m_line; + ItemType m_type; + qreal m_targetPicTop; + + qreal picTop() const { return m_pic->pos().y(); } + void setPicTop(qreal top) { m_pic->setPos(m_pic->pos().x(), top); } + +}; + +#endif // FORECASTHUNGITEM_H diff --git a/weather/src/forecastprovider.cpp b/weather/src/forecastprovider.cpp new file mode 100644 index 0000000..5bda3fe --- /dev/null +++ b/weather/src/forecastprovider.cpp @@ -0,0 +1,54 @@ +#include "forecastprovider.h" +#include <QTime> +#include <QTimer> + +#include "forecast.h" + +ForecastProvider::ForecastProvider() + : m_source(0) +{ +} + +ForecastProvider::~ForecastProvider() +{ +} + +int ForecastProvider::getForecast(const QString &key, bool locationId) +{ + ForecastProvider *obj = instance(); + if (obj->m_source) + return obj->m_source->getForecast(key, locationId); + return -1; +} + +ForecastProvider *ForecastProvider::instance() +{ + static ForecastProvider * const result(new ForecastProvider()); + return result; +} + + +void ForecastProvider::setForecastSource(ForecastSource *source) +{ + ForecastProvider *obj = instance(); + if (obj->m_source) { + disconnect(obj->m_source, SIGNAL(forecastReceived(int, ForecastData)), + obj, SIGNAL(forecastResponse(int, ForecastData))); + obj->m_source->deleteLater(); + } + obj->m_source = source; + if (obj->m_source) + connect(obj->m_source, SIGNAL(forecastReceived(int, ForecastData)), + obj, SIGNAL(forecastResponse(int, ForecastData)), Qt::QueuedConnection); +} + +void ForecastProvider::connectToResponseSignal(QObject *receiver, const char *method) +{ + QObject::connect(instance(), SIGNAL(forecastResponse(int, ForecastData)), + receiver, method, Qt::QueuedConnection); +} + +void ForecastProvider::disconnectReceiver(QObject *receiver) +{ + instance()->disconnect(receiver); +} diff --git a/weather/src/forecastprovider.h b/weather/src/forecastprovider.h new file mode 100644 index 0000000..fcb75dd --- /dev/null +++ b/weather/src/forecastprovider.h @@ -0,0 +1,37 @@ +#ifndef FORECASTPROVIDER_H +#define FORECASTPROVIDER_H + +#include <QObject> +#include <QString> +#include <QStringList> +#include <QList> +#include <QMap> + +#include "forecastdata.h" +#include "forecastsource.h" + +class ForecastProvider : public QObject +{ + Q_OBJECT +public: + static int getForecast(const QString &key, bool locationId); + + static void connectToResponseSignal(QObject *receiver, const char *method); + static void disconnectReceiver(QObject *receiver); + + static void setForecastSource(ForecastSource *source); + +signals: + void forecastResponse(int reqId, const ForecastData &forecast); + +private: + + ForecastSource *m_source; + + ForecastProvider(); + ~ForecastProvider(); + static ForecastProvider *instance(); + +}; + +#endif // FORECASTPROVIDER_H diff --git a/weather/src/forecastrain.cpp b/weather/src/forecastrain.cpp new file mode 100644 index 0000000..81f3175 --- /dev/null +++ b/weather/src/forecastrain.cpp @@ -0,0 +1,122 @@ +/**************************************************************************** +** +** 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 "forecastrain.h" +#include "settings.h" +#include "pixmaploader.h" + +typedef struct +{ + const char * const prefix; + QString name() const { return prefix; } + QPixmap pic() const { return PixmapLoader::getPic(name()); } + +private: + +} RainItemData; + +static const int RainItemCount = 3; +static RainItemData RainItemArray[RainItemCount] = { + {"rain_01"}, + {"rain_02"}, + {"rain_03"} +}; + +ForecastRain::ForecastRain(RainType type, QGraphicsItem *parent) + : QGraphicsItem(parent) + , m_animation(this, "progress") +{ + for (int i = Light; i <= Heavy; ++i) { + QGraphicsPixmapItem *item = new QGraphicsPixmapItem(RainItemArray[i].pic(), this); + item->setPos(0.0, -20.0); + item->hide(); + m_items.append(item); + } + m_animation.setStartValue(0.0); + m_animation.setEndValue(3.0); + m_animation.setDuration(type == Light ? 3000 : type == Medium ? 2000 : 1000); + m_animation.setLoopCount(-1); +} + +int ForecastRain::loadImages() +{ + for (int i = 0; i < RainItemCount;++i) + PixmapLoader::load(RainItemArray[i].name()); + return RainItemCount; +} + +void ForecastRain::start() +{ + switch (m_animation.state()) { + case QAbstractAnimation::Stopped: + m_animation.start(); + break; + case QAbstractAnimation::Paused: + m_animation.resume(); + break; + default: + break; + } +} + +QRectF ForecastRain::boundingRect () const +{ + return m_items.isEmpty() ? QRectF() : m_items[0]->boundingRect(); +} + +void ForecastRain::paint(QPainter *painter, const QStyleOptionGraphicsItem *opt, QWidget *widget) +{ + Q_UNUSED(painter); + Q_UNUSED(opt); + Q_UNUSED(widget); +} + +void ForecastRain::stop() +{ + if (m_items.count() > 1 && m_animation.state() == QAbstractAnimation::Running) + m_animation.pause(); +} + +void ForecastRain::setProgress(qreal progress) +{ + m_progress = progress; + for (int i = 0; i < m_items.count(); ++i) { + + qreal x = progress + i - 1; + x = x < 0 ? 3.0 + x : x > 3.0 ? x - 3.0 : x; + const qreal opacity = x <= 1.0 ? x + : x <= 2.0 ? 2.0 - x + : 0.0; + m_items[i]->setOpacity(opacity); + if ((opacity != 0.0) != m_items[i]->isVisible()) + m_items[i]->setVisible(opacity != 0.0); + } +} diff --git a/weather/src/forecastrain.h b/weather/src/forecastrain.h new file mode 100644 index 0000000..91b5aaf --- /dev/null +++ b/weather/src/forecastrain.h @@ -0,0 +1,76 @@ +/**************************************************************************** +** +** 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 FORECASTRAIN_H +#define FORECASTRAIN_H + +#include <QObject> +#include <QGraphicsItem> +#include <QPropertyAnimation> +#include <QGraphicsPixmapItem> + +#include <QDebug> + +class ForecastRain : public QObject, public QGraphicsItem +{ + Q_OBJECT + Q_INTERFACES(QGraphicsItem) + Q_PROPERTY(qreal progress READ progress WRITE setProgress) + Q_PROPERTY(qreal opacity READ opacity WRITE setOpacity) +public: + enum RainType + { + Light = 0, + Medium, + Heavy + }; + ForecastRain(RainType type, QGraphicsItem *parent = 0); + + static int loadImages(); + + QRectF boundingRect () const; + void paint(QPainter *painter, const QStyleOptionGraphicsItem *option, QWidget *widget = 0); + +public slots: + void start(); + void stop(); + void show() { QGraphicsItem::show(); } + +private: + QList<QGraphicsPixmapItem*> m_items; + QPropertyAnimation m_animation; + qreal m_progress; + + void setProgress(qreal progress); + qreal progress() const { return m_progress; } +}; + +#endif // FORECASTRAIN_H diff --git a/weather/src/forecastsnow.cpp b/weather/src/forecastsnow.cpp new file mode 100644 index 0000000..d342c1e --- /dev/null +++ b/weather/src/forecastsnow.cpp @@ -0,0 +1,200 @@ +/**************************************************************************** +** +** 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 "forecastsnow.h" +#include "settings.h" +#include "pixmaploader.h" + +#include <QPainter> + +typedef struct +{ + const char * const prefix; + QString name() const { return prefix; } + QPixmap pic() const { return PixmapLoader::getPic(name()); } + +private: + +} SnowFlaketData; + +static const int SnowFlakeCount = 10; +static SnowFlaketData SnowFlakeArray[SnowFlakeCount] = { + {"snow_flake"}, + {"snow_flake_01"}, + {"snow_flake_02"}, + {"snow_flake_03"}, + {"snow_flake_04"}, + {"snow_flake_05"}, + {"snow_flake_06"}, + {"snow_flake_07"}, + {"snow_flake_08"}, + {"snow_flake_09"}, +}; + +static const qreal maxVerticalSpeed = 70.0; +static const qreal minVerticalSpeed = 30.0; +static const qreal mediumVerticalSpeed = (maxVerticalSpeed + minVerticalSpeed) / 2; + +static const qreal maxHorizontalSpeed = 100.0; +static const qreal minHorizontalSpeed = 20.0; + +static const qreal windSpeed = -50.0; + +static const qreal speedDeviation = 0.15; + +static const int deviation1 = 2; +static const int deviation2 = 31; + +#define RANDOM_FACTOR (qreal(qrand()) / qreal(RAND_MAX)) + +// ForecastSnow + +ForecastSnow::SnowFlake::SnowFlake(int type, const QSizeF &bounds) + : m_pixmap(SnowFlakeArray[type].pic()) + , m_bounds(bounds) + , m_waitingTime(0) +{ + qreal x = minHorizontalSpeed + windSpeed + + RANDOM_FACTOR * (maxHorizontalSpeed - minHorizontalSpeed); + qreal y = minVerticalSpeed + RANDOM_FACTOR * (maxVerticalSpeed - minVerticalSpeed); + m_speed = QPointF(x, y); +} + +int ForecastSnow::loadImages() +{ + for (int i = 0; i < SnowFlakeCount;++i) + PixmapLoader::load(SnowFlakeArray[i].name()); + return SnowFlakeCount; +} + +static inline bool doDeviation(qreal &factor) +{ + if (qrand() % deviation2 > deviation1) + return false; + factor = 1.0 + ((qrand() % 2) ? speedDeviation : -speedDeviation); + return true; +} + +void ForecastSnow::SnowFlake::timerEvent(int interval_ms) +{ + if (m_waitingTime < 0) { + m_waitingTime += interval_ms; + return; + } + const qreal interval_s = qreal(interval_ms) / 1000.0; + qreal x = m_pos.x() + m_speed.x() * interval_s; + qreal y = m_pos.y() + m_speed.y() * interval_s; + + y = y >= m_bounds.height() ? -m_pixmap.height() : y; + x = x <= -m_pixmap.width() ? m_bounds.width() + : x >= m_bounds.width() ? -m_pixmap.width() : x; + + m_pos = QPointF(x, y); + + qreal factor; + if (doDeviation(factor)) + m_speed.setY(qMax(qMin(m_speed.y() * factor, maxVerticalSpeed), minVerticalSpeed)); + if (doDeviation(factor)) { + qreal x = m_speed.x() - windSpeed; + x = qMax(qMin(x * factor, maxHorizontalSpeed), minHorizontalSpeed) + windSpeed; + m_speed.setX(x); + } +} + +void ForecastSnow::SnowFlake::paint(QPainter *painter) +{ + if (isVisible() && m_waitingTime >= 0) + painter->drawPixmap(m_pos, m_pixmap); +} + +ForecastSnow::ForecastSnow(int count, const QRectF &bounds, QGraphicsItem *parent) + : m_bounds(bounds) + , m_parent(parent) + , m_visible(false) +{ + qsrand(QTime(0, 0).secsTo(QTime::currentTime()) * qrand()); + for (int i = 0; i < count; ++i) + m_flakes.append(new SnowFlake(qrand() % 10, m_bounds.size())); + updateFLakesPositions(); +} + +void ForecastSnow::updateFLakesPositions() +{ + qsrand(QTime(0, 0).secsTo(QTime::currentTime()) * qrand()); + + int maxTime = -1000 * m_bounds.height() / mediumVerticalSpeed; + + foreach(SnowFlake *flake, m_flakes) { + + const qreal height = -flake->height(); + const qreal width = m_bounds.width() - flake->width(); + + flake->setPos(width *RANDOM_FACTOR, height *RANDOM_FACTOR); + flake->setWaitingTime(maxTime * RANDOM_FACTOR); + } +} + +void ForecastSnow::paint(QPainter *painter) +{ + if (!m_visible) + return; + + painter->translate(m_bounds.topLeft()); + + foreach(SnowFlake *flake, m_flakes) + flake->paint(painter); + + painter->translate(QPointF(-m_bounds.left(), -m_bounds.top())); +} + +void ForecastSnow::start() +{ + m_lastTick = QTime::currentTime(); + m_ticker.start(30, this); + show(); +} + +void ForecastSnow::stop() +{ + m_ticker.stop(); +} + +void ForecastSnow::timerEvent(QTimerEvent *event) +{ + Q_UNUSED(event); + QTime now = QTime::currentTime(); + int interval = m_lastTick.msecsTo(now); + m_lastTick = now; + foreach(SnowFlake *flake, m_flakes) + flake->timerEvent(interval); + if (m_parent) + m_parent->update(m_bounds); +} diff --git a/weather/src/forecastsnow.h b/weather/src/forecastsnow.h new file mode 100644 index 0000000..809b232 --- /dev/null +++ b/weather/src/forecastsnow.h @@ -0,0 +1,96 @@ +/**************************************************************************** +** +** 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 FORECASTSNOW_H +#define FORECASTSNOW_H + +#include <QObject> +#include <QGraphicsItem> +#include <QBasicTimer> +#include <QTime> + +class ForecastSnow : public QObject +{ + Q_OBJECT +public: + ForecastSnow(int count, const QRectF &bounds, QGraphicsItem *parent); + + static int loadImages(); + + void paint(QPainter *painter); + +public slots: + void start(); + void stop(); + void show() { m_visible = true; } + void hide() { m_visible = false; } + +protected: + void timerEvent(QTimerEvent *event); + +private: + class SnowFlake + { + public: + SnowFlake(int type, const QSizeF &bounds); + void timerEvent(int interval_ms); + + qreal width() const { return m_pixmap.width(); } + qreal height() const { return m_pixmap.height(); } + + void setPos(qreal x, qreal y) { m_pos = QPointF(x, y); } + void setWaitingTime(int value) { m_waitingTime = value; } + + bool isVisible() const { return !m_pixmap.isNull(); } + + inline void paint(QPainter *painter); + + private: + QPixmap m_pixmap; + const QSizeF m_bounds; + QPointF m_speed; + QPointF m_pos; + int m_waitingTime; + }; + + const QRectF m_bounds; + QGraphicsItem *m_parent; + QList<SnowFlake*> m_flakes; + QBasicTimer m_ticker; + QTime m_lastTick; + + qreal m_visible; + + void updateFLakesPositions(); + +}; + +#endif // FORECASTSNOW_H diff --git a/weather/src/forecastsource.h b/weather/src/forecastsource.h new file mode 100644 index 0000000..8c7cfbe --- /dev/null +++ b/weather/src/forecastsource.h @@ -0,0 +1,18 @@ +#ifndef FORECASTSOURCE_H +#define FORECASTSOURCE_H + +#include <QObject> +#include "forecastdata.h" + +class ForecastSource : public QObject +{ + Q_OBJECT +public: + ForecastSource(QObject *parent = 0) : QObject(parent) {} + virtual int getForecast(const QString &key, bool locationId) = 0; + +signals: + void forecastReceived(int id, const ForecastData &data); +}; + +#endif // FORECASTSOURCE_H diff --git a/weather/src/forecaststars.cpp b/weather/src/forecaststars.cpp new file mode 100644 index 0000000..97379dc --- /dev/null +++ b/weather/src/forecaststars.cpp @@ -0,0 +1,200 @@ +/**************************************************************************** +** +** 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 "forecaststars.h" +#include "settings.h" +#include "pixmaploader.h" + +#include <QTime> +#include <QPropertyAnimation> + +typedef struct +{ + const char * const prefix; + const qreal radius; + QString name() const { return prefix; } + QPixmap pic() const { return PixmapLoader::getPic(name()); } + +private: + +} StarObjectData; + +static const int StarTypesCount = 3; +static StarObjectData StarObjectsData[StarTypesCount] = { + {"star_01", 0.30000}, + {"star_02", 0.22428}, + {"star_03", 0.16666} +}; + +// ForecastStars + +ForecastStars::Star::Star(int type, QGraphicsItem *parent) + : QGraphicsPixmapItem(StarObjectsData[type].pic(), parent) + , starType(type) +{ + setOpacity(0.0); + hide(); +} + +ForecastStars::ForecastStars(int count, QGraphicsItem *parent) + : QGraphicsItem(parent) + , m_progress(1.0) +{ + setFlag(QGraphicsItem::ItemHasNoContents, true); + qsrand(QTime(0, 0).secsTo(QTime::currentTime()) * qrand()); + for (int i = 0; i < count; ++i) + m_starts.append(new Star(qrand() % 3, this)); +} + +int ForecastStars::loadImages() +{ + for (int i = 0; i < StarTypesCount;++i) + PixmapLoader::load(StarObjectsData[i].name()); + return StarTypesCount; +} + +QAbstractAnimation *ForecastStars::getAnimation() +{ + QPropertyAnimation *result = new QPropertyAnimation(this, "progress"); + result->setStartValue(0.0); + result->setEndValue(1.0); + result->setEasingCurve(QEasingCurve::OutBack); + result->setDuration(m_starts.count() * 100); + connect(result, SIGNAL(stateChanged(QAbstractAnimation::State, QAbstractAnimation::State)), + this, SLOT(animationStateChanged(QAbstractAnimation::State, QAbstractAnimation::State))); + return result; +} + +void ForecastStars::animationStateChanged(QAbstractAnimation::State newState, + QAbstractAnimation::State oldState) +{ + if (oldState == QAbstractAnimation::Stopped && newState == QAbstractAnimation::Running) { + foreach (Star *star, m_starts) { + star->setOpacity(0.0); + star->show(); + } + } +} + +void ForecastStars::setRect(QRectF rect) +{ + setPos(rect.topLeft()); + m_boundingRect = rect; + m_boundingRect.moveTo(0.0, 0.0); + updateStarsPositions(); +} + +QRectF ForecastStars::boundingRect () const +{ + return m_boundingRect; +} + +QRectF ForecastStars::rect() const +{ + QRectF result(m_boundingRect); + result.moveTo(pos()); + return result; +} + +void ForecastStars::paint(QPainter *painter, const QStyleOptionGraphicsItem *opt, QWidget *widget) +{ + Q_UNUSED(painter); + Q_UNUSED(opt); + Q_UNUSED(widget); +} + +void ForecastStars::setProgress(qreal progress) +{ + m_progress = progress; + const qreal delta = 1.0 / qreal(m_starts.count()); + + for (int i = 0; i < m_starts.count(); ++i) { + qreal val = progress - i * delta; + m_starts[i]->setOpacity(val < 0.0 ? 0.0 : val > delta ? 1.0 : val / delta); + } +} + +bool ForecastStars::checkColision(const QRectF &rect1, qreal radius1, + const QRectF &rect2, qreal radius2) +{ + QPointF p1(rect1.left() + rect1.width() / 2.0, rect1.top() + rect1.height() / 2.0); + QPointF p2(rect2.left() + rect2.width() / 2.0, rect2.top() + rect2.height() / 2.0); + qreal min = radius1 + radius2; + return qAbs(p1.x() - p2.x()) > min && qAbs(p1.y() - p2.y()) > min; +} + +bool ForecastStars::checkColision(Star *item1, Star *item2) +{ + QRectF r1 = item1->boundingRect(); + r1.moveTo(item1->pos()); + qreal radius1 = item1->boundingRect().width() * StarObjectsData[item1->starType].radius; + + QRectF r2 = item2->boundingRect(); + r2.moveTo(item2->pos()); + qreal radius2 = item2->boundingRect().width() * StarObjectsData[item2->starType].radius; + + return checkColision(r1, radius1, r2, radius2); +} + +bool ForecastStars::checkColision(Star *star, const QList<Star*> &items) +{ + foreach(Star *item, items) { + if (!checkColision(star, item)) + return false; + } + return true; +} + +QPointF ForecastStars::getRandomPos(const QRectF &border) +{ + return QPointF(qreal(qrand()) / qreal(RAND_MAX) * border.width() + border.left(), + qreal(qrand()) / qreal(RAND_MAX) * border.height() + border.top()); +} + +void ForecastStars::updateStarsPositions() +{ + QList<Star*> items; + qsrand(QTime(0, 0).secsTo(QTime::currentTime()) * qrand()); + foreach(Star *star, m_starts) { + const QRectF rect(boundingRect()); + const QRectF starRect(star->boundingRect()); + const QPointF topLeft(rect.left() - starRect.left(), rect.top() - starRect.top()); + const QPointF bottomRight(rect.right() - starRect.right(), + rect.bottom() - starRect.bottom()); + const QRectF border(topLeft, bottomRight); + + int tries = 10; + do + star->setPos(getRandomPos(border)); + while (tries-- && !checkColision(star, items)); + items.append(star); + } +} diff --git a/weather/src/forecaststars.h b/weather/src/forecaststars.h new file mode 100644 index 0000000..23a135c --- /dev/null +++ b/weather/src/forecaststars.h @@ -0,0 +1,83 @@ +/**************************************************************************** +** +** 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 FORECASTSTARS_H +#define FORECASTSTARS_H + +#include <QObject> +#include <QGraphicsItem> +#include <QAbstractAnimation> + +class ForecastStars : public QObject, public QGraphicsItem +{ + Q_OBJECT + Q_INTERFACES(QGraphicsItem) + Q_PROPERTY(qreal progress READ progress WRITE setProgress) +public: + ForecastStars(int count, QGraphicsItem *parent = 0); + QAbstractAnimation *getAnimation(); + + static int loadImages(); + + QRectF rect() const; + void setRect(QRectF rect); + + QRectF boundingRect () const; + void paint(QPainter *painter, const QStyleOptionGraphicsItem *option, QWidget *widget = 0); + +private: + class Star : public QGraphicsPixmapItem + { + public: + Star(int type, QGraphicsItem *parent); + const int starType; + }; + + qreal m_progress; + QRectF m_boundingRect; + QList<Star*> m_starts; + + void setProgress(qreal progress); + qreal progress() const { return m_progress; } + + inline bool checkColision(const QRectF &rect1, qreal radius1, + const QRectF &rect2, qreal radius2); + inline bool checkColision(Star *item1, Star *item2); + inline bool checkColision(Star *star, const QList<Star*> &items); + inline QPointF getRandomPos(const QRectF &border); + void updateStarsPositions(); + +private slots: + void animationStateChanged(QAbstractAnimation::State newState, + QAbstractAnimation::State oldState); +}; + +#endif // FORECASTSTARS_H diff --git a/weather/src/forecastview.cpp b/weather/src/forecastview.cpp new file mode 100644 index 0000000..f7a0d6b --- /dev/null +++ b/weather/src/forecastview.cpp @@ -0,0 +1,643 @@ +/**************************************************************************** +** +** 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 "forecastview.h" +#include "settings.h" + +#include <QPropertyAnimation> +#include <QParallelAnimationGroup> +#include <QSequentialAnimationGroup> + +#include <QDebug> + +// Variations + +struct VariationData +{ + const qreal x; + const qreal y; +}; + +static const int variationCount = 3; +static const VariationData variationArray[variationCount] = +{ + {35.0, 20.0}, + {25.0, 15.0}, + {15.0, 10.0} +}; + +// Sun positions + +struct SunPosition +{ + const ForecastHungItem::ItemType type; + const qreal linePos; + const qreal picTop; +}; + +static const int sunPositionsArrayCount = 3; +static const SunPosition sunPositionsArray[sunPositionsArrayCount] = +{ + {ForecastHungItem::Sun, 240, 124}, + {ForecastHungItem::ColdSun, 240, 205}, + {ForecastHungItem::Moon, 240, 235} +}; + +class ForecastMostlyCloudyView : public ForecastView +{ +public: + ForecastMostlyCloudyView(bool night, QGraphicsItem *parent = 0) + : ForecastView(Forecast::MostlyCloudy, parent) + { + if (night) { + addHungItem(ForecastHungItem::CloudRain1, QPointF(357, 308), 0); + addHungItem(ForecastHungItem::CloudRain2, QPointF(46, 274), 1); + addHungItem(ForecastHungItem::CloudRain2, QPointF(463, 273), 1); + addHungItem(ForecastHungItem::CloudRain3, QPointF(139, 221), 2); + setMainHangItem(ForecastHungItem::Moon); + } else { + addHungItem(ForecastHungItem::Cloud1, QPointF(357, 308), 0); + addHungItem(ForecastHungItem::Cloud2, QPointF(46, 274), 1); + addHungItem(ForecastHungItem::Cloud2, QPointF(463, 273), 1); + addHungItem(ForecastHungItem::Cloud3, QPointF(139, 221), 2); + setMainHangItem(ForecastHungItem::ColdSun); + } + } +}; + +class ForecastCloudyView : public ForecastView +{ +public: + ForecastCloudyView(bool night, QGraphicsItem *parent = 0) + : ForecastView(Forecast::Cloudy, parent) + { + if (night) { + addHungItem(ForecastHungItem::CloudRain1, QPointF(440, 275), 0); + addHungItem(ForecastHungItem::CloudRain1, QPointF(121, 273), 0); + addHungItem(ForecastHungItem::CloudRain2, QPointF(307, 266), 1); + addHungItem(ForecastHungItem::CloudRain3, QPointF(372, 224), 2); + addHungItem(ForecastHungItem::CloudRain3, QPointF(33, 227), 2); + setMainHangItem(ForecastHungItem::Moon); + } else { + addHungItem(ForecastHungItem::Cloud1, QPointF(440, 275), 0); + addHungItem(ForecastHungItem::Cloud1, QPointF(121, 273), 0); + addHungItem(ForecastHungItem::Cloud2, QPointF(307, 266), 1); + addHungItem(ForecastHungItem::Cloud3, QPointF(372, 224), 2); + addHungItem(ForecastHungItem::Cloud3, QPointF(33, 227), 2); + setMainHangItem(ForecastHungItem::ColdSun); + } + } +}; + +class ForecastMostlySunnyView : public ForecastView +{ +public: + ForecastMostlySunnyView(bool night, QGraphicsItem *parent = 0) + : ForecastView(Forecast::MostlySunny, parent) + { + if (night) { + addHungItem(ForecastHungItem::CloudRain2, QPointF(72, 389), 1); + addHungItem(ForecastHungItem::CloudRain3, QPointF(454, 295), 2); + addHungItem(ForecastHungItem::CloudRain3, QPointF(48, 164), 2); + setMainHangItem(ForecastHungItem::Moon); + addStars(); + } else { + addHungItem(ForecastHungItem::Cloud2, QPointF(72, 389), 1); + addHungItem(ForecastHungItem::Cloud3, QPointF(454, 295), 2); + addHungItem(ForecastHungItem::Cloud3, QPointF(48, 164), 2); + setMainHangItem(ForecastHungItem::Sun); + } + } +}; + +class ForecastPartlyCloudyView : public ForecastView +{ +public: + ForecastPartlyCloudyView(bool night, QGraphicsItem *parent = 0) + : ForecastView(Forecast::PartlyCloudy, parent) + { + if (night) { + addHungItem(ForecastHungItem::CloudRain1, QPointF(389, 315), 0); + addHungItem(ForecastHungItem::CloudRain2, QPointF(92, 300), 1); + addHungItem(ForecastHungItem::CloudRain3, QPointF(451, 258), 2); + setMainHangItem(ForecastHungItem::Moon); + } else { + addHungItem(ForecastHungItem::Cloud1, QPointF(389, 315), 0); + addHungItem(ForecastHungItem::Cloud2, QPointF(92, 300), 1); + addHungItem(ForecastHungItem::Cloud3, QPointF(451, 258), 2); + setMainHangItem(ForecastHungItem::Sun); + } + } +}; + +class ForecastSunnyView : public ForecastView +{ +public: + ForecastSunnyView(bool night, QGraphicsItem *parent = 0) + : ForecastView(Forecast::Sunny, parent) + { + if (night) { + setMainHangItem(ForecastHungItem::Moon); + addStars(); + } else { + setMainHangItem(ForecastHungItem::Sun); + } + } +}; + +class ForecastFlurriesView : public ForecastView +{ +public: + ForecastFlurriesView(bool night, QGraphicsItem *parent = 0) + : ForecastView(Forecast::Flurries, parent) + { + setMainHangItem(night ? ForecastHungItem::Moon : ForecastHungItem::ColdSun); + addHungItem(ForecastHungItem::CloudRain1, QPointF(113, 300), 0); + addHungItem(ForecastHungItem::CloudRain1, QPointF(382, 267), 0, true); + addHungItem(ForecastHungItem::CloudRain2, QPointF(72, 243), 1); + addHungItem(ForecastHungItem::CloudRain3, QPointF(349, 226), 2); + addSnow(10); + } +}; + +class ForecastFogView : public ForecastView +{ +public: + ForecastFogView(bool night, QGraphicsItem *parent = 0) + : ForecastView(Forecast::Fog, parent) + { + setMainHangItem(night ? ForecastHungItem::Moon : ForecastHungItem::ColdSun); + addHungItem(ForecastHungItem::CloudRain2, QPointF(95, 311), 1); + addHungItem(ForecastHungItem::CloudRain3, QPointF(363, 236), 2); + } +}; + +class ForecastHazeView : public ForecastView +{ + // haze || sand || dust +public: + ForecastHazeView(Forecast::ForecastType type, bool night, QGraphicsItem *parent = 0) + : ForecastView(type, parent) + { + setMainHangItem(night ? ForecastHungItem::Moon : ForecastHungItem::ColdSun); + addHungItem(ForecastHungItem::CloudRain2, QPointF(98, 311), 1); + addHungItem(ForecastHungItem::CloudRain3, QPointF(396, 235), 2); + } +}; + +class ForecastIcyView : public ForecastView +{ +public: + ForecastIcyView(bool night, QGraphicsItem *parent = 0) + : ForecastView(Forecast::Icy, parent) + { + setMainHangItem(night ? ForecastHungItem::Moon : ForecastHungItem::ColdSun); + addHungItem(ForecastHungItem::CloudRain1, QPointF(113, 300), 0); + addHungItem(ForecastHungItem::CloudRain1, QPointF(382, 267), 0, true); + addHungItem(ForecastHungItem::CloudRain2, QPointF(72, 243), 1); + addHungItem(ForecastHungItem::CloudRain3, QPointF(349, 226), 2); + addSnow(10); + } +}; + +class ForecastSleetView : public ForecastView +{ +public: + ForecastSleetView(bool night, QGraphicsItem *parent = 0) + : ForecastView(Forecast::Sleet, parent) + { + setMainHangItem(night ? ForecastHungItem::Moon : ForecastHungItem::ColdSun); + addHungItem(ForecastHungItem::CloudRain1, QPointF(135, 317), 0); + addHungItem(ForecastHungItem::CloudRain2, QPointF(341, 313), 1, true); + addHungItem(ForecastHungItem::CloudRain2, QPointF(72, 276), 1); + addHungItem(ForecastHungItem::CloudRain3, QPointF(199, 250), 2); + addHungItem(ForecastHungItem::CloudRain3, QPointF(417, 268), 2); + addSnow(10); + addRain(ForecastRain::Light); + } +}; + +class ForecastSnowView : public ForecastView +{ +public: + ForecastSnowView(bool night, QGraphicsItem *parent = 0) + : ForecastView(Forecast::Snow, parent) + { + setMainHangItem(night ? ForecastHungItem::Moon : ForecastHungItem::ColdSun); + addHungItem(ForecastHungItem::CloudRain1, QPointF(46, 325), 0); + addHungItem(ForecastHungItem::CloudRain1, QPointF(359, 302), 0, true); + addHungItem(ForecastHungItem::CloudRain2, QPointF(158, 288), 1); + addHungItem(ForecastHungItem::CloudRain3, QPointF(73, 267), 2); + addHungItem(ForecastHungItem::CloudRain3, QPointF(401, 273), 2); + addSnow(20); + } +}; + +class ForecastMistView : public ForecastView +{ +public: + ForecastMistView(bool night, QGraphicsItem *parent = 0) + : ForecastView(Forecast::Mist, parent) + { + setMainHangItem(night ? ForecastHungItem::Moon : ForecastHungItem::ColdSun); + addHungItem(ForecastHungItem::CloudRain1, QPointF(52, 312), 0); + addHungItem(ForecastHungItem::CloudRain1, QPointF(437, 255), 0); + addHungItem(ForecastHungItem::CloudRain2, QPointF(246, 311), 1, true); + addHungItem(ForecastHungItem::CloudRain3, QPointF(119, 268), 2); + addHungItem(ForecastHungItem::CloudRain3, QPointF(352, 244), 2); + addRain(ForecastRain::Light); + } +}; + +class ForecastRainView : public ForecastView +{ +public: + ForecastRainView(bool night, QGraphicsItem *parent = 0) + : ForecastView(Forecast::Rain, parent) + { + setMainHangItem(night ? ForecastHungItem::Moon : ForecastHungItem::ColdSun); + addHungItem(ForecastHungItem::CloudRain1, QPointF(60, 317), 0); + addHungItem(ForecastHungItem::CloudRain1, QPointF(308, 287), 0, true); + addHungItem(ForecastHungItem::CloudRain2, QPointF(192, 251), 1); + addHungItem(ForecastHungItem::CloudRain2, QPointF(432, 268), 1); + addHungItem(ForecastHungItem::CloudRain3, QPointF(355, 235), 2); + addRain(ForecastRain::Medium); + } +}; + +class ForecastStormView : public ForecastView +{ +public: + ForecastStormView(bool night, QGraphicsItem *parent = 0) + : ForecastView(Forecast::Storm, parent) + { + setMainHangItem(night ? ForecastHungItem::Moon : ForecastHungItem::ColdSun); + addHungItem(ForecastHungItem::CloudStorm1, QPointF(324, 296), 0); + addHungItem(ForecastHungItem::CloudStorm1, QPointF(91, 261), 0, true); + addHungItem(ForecastHungItem::CloudStorm2, QPointF(432, 257), 1); + addHungItem(ForecastHungItem::CloudStorm3, QPointF(92, 210), 2); + addHungItem(ForecastHungItem::CloudStorm3, QPointF(334, 224), 2); + addRain(ForecastRain::Heavy); + } +}; + +class ForecastThunderstormView : public ForecastView +{ +public: + ForecastThunderstormView(bool night, QGraphicsItem *parent = 0) + : ForecastView(Forecast::Thunderstorm, parent) + { + setMainHangItem(night ? ForecastHungItem::Moon : ForecastHungItem::ColdSun); + addHungItem(ForecastHungItem::CloudTStorm1, QPointF(371, 303), 0); + addHungItem(ForecastHungItem::CloudStorm1, QPointF(110, 283), 0, true); + addHungItem(ForecastHungItem::CloudTStorm2, QPointF(207, 272), 1); + addHungItem(ForecastHungItem::CloudStorm2, QPointF(367, 248), 1); + addHungItem(ForecastHungItem::CloudStorm3, QPointF(111, 223), 2); + addRain(ForecastRain::Heavy); + } +}; + +static const qreal minHungItemsZ = -1.0; +static const qreal maxHungItemsZ = 0.0; +static const qreal sunZ = (minHungItemsZ + maxHungItemsZ) / 2.0 + 0.01; +static const qreal starsZ = -10.0; + +static const qreal radius = (maxHungItemsZ - minHungItemsZ) * 1; +static const qreal maxDisplacementFactor = 1.5; + +// HungItemsManager + +static QAbstractAnimation *addPause(QAbstractAnimation *animation, int pause_ms) +{ + if (!animation || pause_ms <= 0) + return animation; + QSequentialAnimationGroup *result = new QSequentialAnimationGroup(); + result->addPause(pause_ms); + result->addAnimation(animation); + return result; +} + +void HungItemsManager::reset() +{ + for (int i = 0; i < m_items.count(); ++i) { + m_items[i]->reset(); + m_items[i]->setPos(m_positions[i], 0.0); + } + if (m_sun) { + m_sun->reset(); + m_sun->setPos(m_sunPos, 0.0); + } +} + +QAbstractAnimation *HungItemsManager::getAnimation() +{ + QList<QAbstractAnimation*> list; + const qreal diff = (m_maxZ - m_minZ) / qreal(m_items.count()); + + for (int i = 0; i < m_items.count(); ++i) { + m_items[i]->setZValue(m_maxZ - i * diff); + QAbstractAnimation *animation = m_items[i]->getAnimation(); + if (animation) + list.append(animation); + } + + QAbstractAnimation *clouds = 0; + + switch (list.count()) { + case 0: break; + case 1: + clouds = list[0]; + break; + default: { + QParallelAnimationGroup *animation = new QParallelAnimationGroup(); + for (int i = 0; i < list.count(); ++i) + animation->addAnimation(addPause(list[i], i * 200)); + clouds = animation; + } + } + + QAbstractAnimation *sun = m_sun ? m_sun->getAnimation() : 0; + + if (clouds && m_sun) { + QSequentialAnimationGroup *result = new QSequentialAnimationGroup(); + result->addAnimation(clouds); + result->addAnimation(sun); + return result; + } + return clouds ? clouds : sun; +} + +void HungItemsManager::addItem(ForecastHungItem *item) +{ + m_items.append(item); + m_positions.append(item->pos().x()); +} + +void HungItemsManager::setSun(ForecastHungItem *sun) +{ + m_sun = sun; + m_sunPos = sun->pos().x(); +} + +void HungItemsManager::doSetDisplacement(ForecastHungItem *item, qreal max, qreal pos) +{ + const qreal itemRadius = radius + item->zValue(); + const qreal itemDisplacement = (itemRadius / radius) * max * m_displacement; + item->setPos(pos + itemDisplacement, 0.0); +} + +void HungItemsManager::setElementsDisplacement(qreal displacement) +{ + const qreal max = maxDisplacementFactor * Settings::windowSize().width(); + m_displacement = qMin(qMax(displacement, qreal(-1.0)), qreal(1.0)); + for (int i = 0; i < m_items.count(); ++i) + doSetDisplacement(m_items[i], max, m_positions[i]); + if (m_sun) + doSetDisplacement(m_sun, max, m_sunPos); +} + +// ForecastView + +void ForecastView::setElementsDisplacement(qreal displacement) +{ + m_hungManager.setElementsDisplacement(displacement); +} + +ForecastView *ForecastView::createView(Forecast::ForecastType type, bool night, QGraphicsItem *parent) +{ + switch (type) { + case Forecast::MostlyCloudy : return new ForecastMostlyCloudyView(night, parent); + case Forecast::Cloudy : return new ForecastCloudyView(night, parent); + case Forecast::MostlySunny : return new ForecastMostlySunnyView(night, parent); + case Forecast::PartlyCloudy : return new ForecastPartlyCloudyView(night, parent); + case Forecast::Sunny : return new ForecastSunnyView(night, parent); + case Forecast::Flurries : return new ForecastFlurriesView(night, parent); + case Forecast::Fog : return new ForecastFogView(night, parent); + case Forecast::Haze : return new ForecastHazeView(Forecast::Haze, night, parent); + case Forecast::Sand : return new ForecastHazeView(Forecast::Sand, night, parent); + case Forecast::Dust : return new ForecastHazeView(Forecast::Dust, night, parent); + case Forecast::Icy : return new ForecastIcyView(night, parent); + case Forecast::Sleet : return new ForecastSleetView(night, parent); + case Forecast::ChanceOfSleet : return new ForecastSleetView(night, parent); + case Forecast::Snow : return new ForecastSnowView(night, parent); + case Forecast::ChanceOfSnow : return new ForecastSnowView(night, parent); + case Forecast::Mist : return new ForecastMistView(night, parent); + case Forecast::Rain : return new ForecastRainView(night, parent); + case Forecast::ChanceOfRain : return new ForecastRainView(night, parent); + case Forecast::Storm : return new ForecastStormView(night, parent); + case Forecast::ChanceOfStorm : return new ForecastStormView(night, parent); + case Forecast::Thunderstorm : return new ForecastThunderstormView(night, parent); + case Forecast::ChanceOfThunderstorm : return new ForecastThunderstormView(night, parent); + default : return 0; + } + +} + +ForecastView::ForecastView(Forecast::ForecastType type, bool night, QGraphicsItem *parent) + : QGraphicsItem(parent) + , m_type(type) + , m_night(night) + , m_boundingRect(QPointF(0.0, 0.0), Settings::windowSize()) + , m_hungManager(minHungItemsZ, maxHungItemsZ) + , m_stars(0) + , m_reference(0) + , m_rain(0) + , m_snow(0) +{ +} + +ForecastView::~ForecastView() +{ + delete m_snow; +} + +QRectF ForecastView::boundingRect () const +{ + return m_boundingRect; +} + +void ForecastView::paint(QPainter *painter, const QStyleOptionGraphicsItem *opt, QWidget *widget) +{ + Q_UNUSED(opt); + Q_UNUSED(widget); + if (m_snow) + m_snow->paint(painter); +} + +static const qreal cloudAdjustment = 20.0; + +void ForecastView::addHungItem(ForecastHungItem::ItemType type, const QPointF &itemPos, + int deviation, bool reference) +{ + const bool validDev = deviation >= 0 && deviation < variationCount; + + qreal devX = validDev ? variationArray[deviation].x : 0.0; + qreal devY = validDev ? variationArray[deviation].y : 0.0; + + devX *= 1.0 - qreal(qrand()) / qreal(RAND_MAX >> 1); + devY *= 1.0 - qreal(qrand()) / qreal(RAND_MAX >> 1); + + ForecastHungItem *item = new ForecastHungItem(type, this); + item->setLinePos(Settings::scaleWidth(itemPos.x() + devX - cloudAdjustment)); + item->setPicPos(item->pos().x(), Settings::scaleHeight(itemPos.y() + devY)); + + if (reference) + m_reference = item; + + m_hungManager.addItem(item); +} + +void ForecastView::reset() +{ + m_hungManager.reset(); + if (m_rain) { + m_rain->stop(); + m_rain->hide(); + } + if (m_snow) { + m_snow->stop(); + m_snow->hide(); + } +} + +void ForecastView::addStars() +{ + if (!m_stars) { + m_stars = new ForecastStars(8, this); + QRectF rect = m_boundingRect; + rect.setHeight(Settings::scaleHeight(522.0)); + m_stars->setRect(rect); + m_stars->setZValue(starsZ); + } +} + +QRectF ForecastView::getEffectRect() +{ + QRectF result = m_boundingRect; + if (m_reference) { + QRectF ref(m_reference->boundingRect()); + result.setTop(ref.bottom()); + result.setHeight(m_boundingRect.height() - ref.bottom()); + } + return result; +} + +template<class T> QAbstractAnimation *ForecastView::createEffectAnimation(T *effect) +{ + effect->setOpacity(0.0); + EffectAnimation *result = new EffectAnimation(); + result->setTargetObject(effect); + result->setPropertyName("opacity"); + result->setStartValue(0.0); + result->setEndValue(1.0); + result->setDuration(200); + result->setEasingCurve(QEasingCurve::InQuart); + connect(result, SIGNAL(started()), effect, SLOT(show())); + connect(result, SIGNAL(finished()), effect, SLOT(start())); + return result; +} + +void ForecastView::addRain(ForecastRain::RainType type) +{ + if (!m_rain) { + m_rain = new ForecastRain(type, this); + m_rain->setPos(getEffectRect().topLeft()); + m_rain->hide(); + m_rain->setZValue(-1); + } +} + +void ForecastView::addSnow(int count) +{ + if (!m_snow) { + m_snow = new ForecastSnow(count, getEffectRect(), this); + m_snow->hide(); + } +} + +void ForecastView::setMainHangItem(ForecastHungItem::ItemType type) +{ + if (!m_hungManager.sun()) { + for (int i = 0; i < sunPositionsArrayCount; ++i) { + if (sunPositionsArray[i].type == type ){ + ForecastHungItem *sun = new ForecastHungItem(type, this); + sun->setPicPos(0.0, Settings::scaleHeight(sunPositionsArray[i].picTop)); + sun->setLinePos(Settings::scaleWidth(sunPositionsArray[i].linePos)); + sun->setZValue(sunZ); + m_hungManager.setSun(sun); + break; + } + } + } +} + +QAbstractAnimation *ForecastView::getAnimation() +{ + QList<QAbstractAnimation*> list; + + QAbstractAnimation *animation = m_hungManager.getAnimation(); + if (animation) + list.append(animation); + + animation = m_stars ? m_stars->getAnimation() : 0; + if (animation) + list.append(animation); + + animation = m_rain ? createEffectAnimation(m_rain) : 0; + if (animation) + list.append(animation); + + QAbstractAnimation *result = 0; + + switch (list.count()) { + case 0: break; + case 1: + result = list[0]; + break; + default: { + QSequentialAnimationGroup *group = new QSequentialAnimationGroup(); + foreach (QAbstractAnimation *a, list) + group->addAnimation(a); + result = group; + } + } + + if (result && m_snow) + connect(result, SIGNAL(finished()), m_snow, SLOT(start())); + else + if (m_snow) + m_snow->show(); + return result; +} + + + + diff --git a/weather/src/forecastview.h b/weather/src/forecastview.h new file mode 100644 index 0000000..4378a59 --- /dev/null +++ b/weather/src/forecastview.h @@ -0,0 +1,133 @@ +/**************************************************************************** +** +** 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 FORECASTVIEW_H +#define FORECASTVIEW_H + +#include <QObject> +#include <QGraphicsItem> +#include <QAbstractAnimation> + +#include "forecasthungitem.h" +#include "forecastsnow.h" +#include "forecastrain.h" +#include "forecaststars.h" +#include "forecast.h" + +class HungItemsManager +{ +public: + HungItemsManager(qreal minZ, qreal maxZ) + : m_minZ(minZ) + , m_maxZ(maxZ) + , m_displacement(0.0) + , m_sun(0) + , m_sunPos(0) {} + void setSun(ForecastHungItem *sun); + ForecastHungItem *sun() { return m_sun; } + void addItem(ForecastHungItem *item); + QAbstractAnimation *getAnimation(); + void reset(); + void setElementsDisplacement(qreal displacement); +private: + const qreal m_minZ; + const qreal m_maxZ; + qreal m_displacement; + ForecastHungItem *m_sun; + qreal m_sunPos; + QList<ForecastHungItem*> m_items; + QList<qreal> m_positions; + + void doSetDisplacement(ForecastHungItem *item, qreal max, qreal pos); +}; + +class EffectAnimation : public QPropertyAnimation +{ + Q_OBJECT +public: +signals: + void started(); +protected: + void updateState(QAbstractAnimation::State oldState, QAbstractAnimation::State newState) + { + if (oldState == Stopped && newState == Running) + emit started(); + } + +private: +}; + +class ForecastView : public QObject, public QGraphicsItem +{ + Q_OBJECT + Q_INTERFACES(QGraphicsItem) +public: + ~ForecastView(); + QRectF boundingRect () const; + void paint(QPainter *painter, const QStyleOptionGraphicsItem *option, QWidget *widget = 0); + Forecast::ForecastType viewType() const { return m_type; } + bool night() const { return m_night; } + + void setElementsDisplacement(qreal displacement); + + void reset(); + QAbstractAnimation *getAnimation(); + + static ForecastView *createView(Forecast::ForecastType type, bool night, QGraphicsItem *parent = 0); + + +protected: + ForecastView(Forecast::ForecastType type, bool night, QGraphicsItem *parent = 0); + + void addHungItem(ForecastHungItem::ItemType type, const QPointF &itemPos, + int deviation, bool reference = false); + void setMainHangItem(ForecastHungItem::ItemType); + void addStars(); + + void addSnow(int count); + void addRain(ForecastRain::RainType); + +private: + const Forecast::ForecastType m_type; + const bool m_night; + const QRectF m_boundingRect; + HungItemsManager m_hungManager; + ForecastStars *m_stars; + ForecastHungItem *m_reference; + ForecastRain *m_rain; + ForecastSnow *m_snow; + + QRectF getEffectRect(); + template<class T> QAbstractAnimation *createEffectAnimation(T *effect); + +}; + +#endif // FORECASTVIEW_H diff --git a/weather/src/gesturebox.cpp b/weather/src/gesturebox.cpp new file mode 100644 index 0000000..cd348bf --- /dev/null +++ b/weather/src/gesturebox.cpp @@ -0,0 +1,392 @@ +/**************************************************************************** +** +** 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 "gesturebox.h" + +#include <QApplication> +#include <QGraphicsView> +#include <QMouseEvent> +#include <QDebug> +#include <QPointer> +#include <QTime> + +#include "gesturebox_p.h" + +// GestureData + +#ifdef QT_ARCH_ARM +static const int move_threshold = 30; +#else +static const int move_threshold = 5; +#endif + +GestureData::GestureData(QPoint pos, const QList<GestureBox*> &boxes, + QGraphicsView *view, QList<QEvent*> &ignoreList, bool sendClickEvents) + : m_state(Pressed) + , m_view(view) + , m_pressPos(pos) + , m_scenePressPos(m_view->mapToScene(m_pressPos)) + , m_boxes(boxes) + , m_ignoreList(ignoreList) + , m_time(QTime::currentTime()) + , m_currentScenePos(m_scenePressPos) + , m_speed(0.0, 0.0) + , m_sendClickEvents(sendClickEvents) +{ +} + +void GestureData::sendClick() +{ + if (m_sendClickEvents) { + QMouseEvent *press = new QMouseEvent(QEvent::MouseButtonPress, + m_pressPos, Qt::LeftButton, + Qt::LeftButton, Qt::NoModifier); + QMouseEvent *release = new QMouseEvent(QEvent::MouseButtonRelease, + m_pressPos, Qt::LeftButton, + Qt::LeftButton, Qt::NoModifier); + m_ignoreList << press; + m_ignoreList << release; + QApplication::postEvent(m_view->viewport(), press); + QApplication::postEvent(m_view->viewport(), release); + } +} + +void GestureData::release(QPoint pos) +{ + Q_UNUSED(pos); + switch (m_state) { + case Pressed: + sendClick(); + break; + case Moving: + foreach(GestureBox *box, m_boxes) + box->gestureEnd(box->mapFromScene(m_currentScenePos), m_speed); + break; + default: + break; + } + m_state = Ended; + deleteLater(); +} + +void GestureData::move(QPoint pos) +{ + if (m_state == Pressed && + abs(pos.x() - m_pressPos.x()) < move_threshold && + abs(pos.y() - m_pressPos.y()) < move_threshold) { + return; + } + QTime time(QTime::currentTime()); + QPointF scenePos = m_view->mapToScene(pos); + QPointF move = scenePos - m_currentScenePos; + qreal secs = (qreal)(m_time.msecsTo(time)) / 1000; + m_currentScenePos = scenePos; + m_speed = QPointF(move.x() / secs, move.y() / secs); + m_time = time; + + switch (m_state) { + case Pressed: + foreach(GestureBox *box, m_boxes) { + box->gestureStart(box->mapFromScene(m_scenePressPos)); + box->gestureMove(box->mapFromScene(m_currentScenePos), move, m_speed); + } + m_state = Moving; + break; + case Moving: + foreach(GestureBox *box, m_boxes) + box->gestureMove(box->mapFromScene(m_currentScenePos), move, m_speed); + break; + default: + break; + } +} + +// ViewData + +ViewData::~ViewData() +{ + if (m_gesture) + m_gesture->deleteLater(); +} + +bool ViewData::ignoreEvent(QEvent *event) +{ + return m_ignored.removeAll(event); +} + +bool ViewData::startGesture(GestureBox *box, QPointF scenePos, bool &sendClick) +{ + bool result = true; + bool click = true; + box->gestureMousePress(box->mapFromScene(scenePos), result, click); + if (result && !click) + sendClick = false; + return result; +} + +bool ViewData::press(QPoint pos) +{ + if (m_gesture && !m_gesture->ended()) { + qWarning() << "ERROR: unexpected mouse press during gesture."; + return false; + } + + if (!m_view || !m_view->scene()) + return false; + + QList<GestureBox*> boxes; + bool sendClick = true; + QPointF scenePos = m_view->mapToScene(pos); + + QList<QGraphicsItem*> itemList = m_view->scene()->items(scenePos, + Qt::IntersectsItemBoundingRect, + Qt::AscendingOrder); + + foreach(QGraphicsItem *item, itemList) { + GestureBox *box = dynamic_cast<GestureBox*>(item); + if (box && startGesture(box, scenePos, sendClick)) + boxes.append(box); + } + + if (boxes.isEmpty()) + return false; + + m_gesture = new GestureData(pos, boxes, m_view, m_ignored, sendClick); + return true; + +} + +bool ViewData::release(QPoint pos) +{ + if (!m_gesture || m_gesture->ended()) + return false; + + m_gesture->release(pos); + return true; +} + +bool ViewData::move(QPoint pos) +{ + if (!m_gesture || m_gesture->ended()) + return false; + + m_gesture->move(pos); + return true; +} + +QList<GestureBox*> ViewData::getBoxList(QPoint pos) { + QList<GestureBox*> result; + if (m_view->scene()) { + QPointF scenePos = m_view->mapToScene(pos); + QGraphicsItem *item = m_view->scene()->itemAt(scenePos); + while (item) { + GestureBox *box = dynamic_cast<GestureBox*>(item); + if (box && box->boundingRect().contains(box->mapFromScene(scenePos))) + result.append(box); + item = item->parentItem(); + } + } + return result; +} + +// GestureObserver + +GestureObserver *GestureObserver::instance() +{ + static GestureObserver *obj = new GestureObserver(); + return obj; +} + +void GestureObserver::addBox(GestureBox *box) +{ + if (box->scene()) { + foreach(QGraphicsView *view, box->scene()->views()) { + ViewData *data = getViewData(view); + if (data->m_boxes.indexOf(box) < 0) { + data->m_boxes.append(box); + m_boxes.insertMulti(box, data); + } + } + } +} + +void GestureObserver::removeBox(GestureBox *box) +{ + QList<ViewData*> dataList = m_boxes.values(box); + foreach(ViewData* data, dataList) { + data->m_boxes.removeAll(box); + if (data->m_boxes.count() == 0) { + m_views.remove(data->view()->viewport()); + data->view()->viewport()->removeEventFilter(this); + delete data; + } + } + m_boxes.remove(box); +} + + +ViewData *GestureObserver::getViewData(QGraphicsView *view) +{ + if (m_views.contains(view->viewport())) + return m_views.value(view->viewport()); + ViewData *data = new ViewData(view); + view->viewport()->installEventFilter(this); + connect(view->viewport(), SIGNAL(destroyed()), this, SLOT(viewDestroyed())); + m_views.insert(view->viewport(), data); + return data; +} + +void GestureObserver::viewDestroyed() +{ + if (!sender()->isWidgetType()) + return; + QWidget *viewport = qobject_cast<QWidget*>(sender()); + if (viewport || m_views.contains(viewport)) { + ViewData *data = m_views.take(viewport); + if (data) { + foreach (GestureBox *box, data->m_boxes) { + QList<ViewData*> boxes = m_boxes.values(box); + m_boxes.remove(box); + + foreach(ViewData *viewData, boxes) { + if (viewData->widget() != viewport) + m_boxes.insertMulti(box, viewData); + } + } + delete data; + } + } + +} + +bool GestureObserver::eventFilter(QObject *object, QEvent *event) +{ + if (!object->isWidgetType()) + return false; + + QEvent::Type type = event->type(); + if (type != QEvent::MouseButtonPress && type != QEvent::MouseButtonRelease && + type != QEvent::MouseMove) + return false; + + QMouseEvent *mouseEvent = dynamic_cast<QMouseEvent*>(event); + if (!mouseEvent || mouseEvent->modifiers() != Qt::NoModifier) + return false; + + QWidget *view = dynamic_cast<QWidget*>(object); + if (!view || !m_views.contains(view)) + return false; + + ViewData *data = m_views[view]; + if (!data || data->ignoreEvent(event)) + return false; + + bool consumed = false; + + switch (mouseEvent->type()) { + case QEvent::MouseButtonPress: + consumed = mouseEvent->buttons() == Qt::LeftButton && data->press(mouseEvent->pos()); + break; + case QEvent::MouseButtonRelease: + consumed = data->release(mouseEvent->pos()); + break; + case QEvent::MouseMove: + consumed = data->move(mouseEvent->pos()); + break; + default: + break; + } + return consumed; +} + +// GestureBox + +GestureBox::GestureBox(QGraphicsItem *parent) + : QGraphicsItem(parent) +{ + GestureObserver::instance()->addBox(this); +} + +GestureBox::~GestureBox() +{ + GestureObserver::instance()->removeBox(this); +} + +QRectF GestureBox::rect() const +{ + QRectF result(m_boundingRect); + result.moveTo(pos()); + return result; +} + +void GestureBox::setRect(QRectF value) +{ + setPos(value.topLeft()); + value.moveTo(0.0, 0.0); + if (value != m_boundingRect) { + prepareGeometryChange(); + m_boundingRect = value; + update(); + } +} + +void GestureBox::setRect(qreal x, qreal y, qreal width, qreal height) +{ + setRect(QRectF(x, y, width, height)); +} + +QRectF GestureBox::boundingRect() const +{ + return m_boundingRect; +} + +void GestureBox::paint(QPainter *painter, + const QStyleOptionGraphicsItem *option, QWidget *widget) +{ + Q_UNUSED(painter); + Q_UNUSED(option); + Q_UNUSED(widget); +} + +QVariant GestureBox::itemChange(GraphicsItemChange change, const QVariant &value) +{ + switch (change) { + case QGraphicsItem::ItemSceneHasChanged: + case QGraphicsItem::ItemParentHasChanged: + GestureObserver::instance()->addBox(this); + break; + default: + break; + } + return value; +} + + diff --git a/weather/src/gesturebox.h b/weather/src/gesturebox.h new file mode 100644 index 0000000..a754105 --- /dev/null +++ b/weather/src/gesturebox.h @@ -0,0 +1,64 @@ +/**************************************************************************** +** +** 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 GESTUREBOX_H +#define GESTUREBOX_H + +#include <QGraphicsItem> + +class GestureBox : public QGraphicsItem +{ +public: + GestureBox(QGraphicsItem *parent = 0); + ~GestureBox(); + + QRectF rect() const; + void setRect(QRectF); + void setRect(qreal, qreal, qreal, qreal); + QRectF boundingRect() const; + +protected: + virtual void gestureMousePress(QPointF pos, bool &startGesture, bool &acceptClick) = 0; + virtual void gestureStart(QPointF pos) = 0; + virtual void gestureMove(QPointF pos, QPointF movement, QPointF speed) = 0; + virtual void gestureEnd(QPointF pos, QPointF speed) = 0; + + void paint(QPainter *painter, const QStyleOptionGraphicsItem *option, QWidget *widget = 0); + QVariant itemChange(GraphicsItemChange change, const QVariant &value); + +private: + friend class GestureData; + friend class ViewData; + QRectF m_boundingRect; + +}; + +#endif // GESTUREBOX_H diff --git a/weather/src/gesturebox_p.h b/weather/src/gesturebox_p.h new file mode 100644 index 0000000..0daa89e --- /dev/null +++ b/weather/src/gesturebox_p.h @@ -0,0 +1,120 @@ +/**************************************************************************** +** +** 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 GESTUREBOX_P_H +#define GESTUREBOX_P_H + +#include <QObject> +#include <QPointer> +#include <QTime> +#include <QPoint> +#include <QPointF> +#include <QGraphicsView> + +#include "gesturebox.h" + +class GestureData : public QObject +{ +public: + inline GestureData(QPoint pos, const QList<GestureBox*> &boxes, QGraphicsView *view, + QList<QEvent*> &ignoreList, bool sendClickEvents); + + inline void release(QPoint pos); + inline void move(QPoint pos); + inline bool ended() const { return m_state == Ended; } + +private: + enum State {Pressed, Moving, Ended}; + State m_state; + QGraphicsView * const m_view; + const QPoint m_pressPos; + const QPointF m_scenePressPos; + const QList<GestureBox*> m_boxes; + QList<QEvent*> &m_ignoreList; + QTime m_time; + QPointF m_currentScenePos; + QPointF m_speed; + bool m_sendClickEvents; + + void sendClick(); +}; + +class ViewData +{ +public: + ViewData(QGraphicsView *view) : m_view(view), m_gesture(0), m_widget(view->viewport()) {} + inline ~ViewData(); + + inline bool press(QPoint pos); + inline bool release(QPoint pos); + inline bool move(QPoint pos); + + QGraphicsView *view() { return m_view; } + QWidget *widget() { return m_widget; } + inline bool ignoreEvent(QEvent *event); + + QList<GestureBox*> m_boxes; + +private: + QGraphicsView *const m_view; + QPointer<GestureData> m_gesture; + QList<QEvent*> m_ignored; + QWidget * const m_widget; + + inline QList<GestureBox*> getBoxList(QPoint pos); + inline bool startGesture(GestureBox *box, QPointF scenePos, bool &sendClick); +}; + +class GestureObserver : public QObject +{ + Q_OBJECT +public: + static inline GestureObserver *instance(); + inline void addBox(GestureBox *box); + inline void removeBox(GestureBox *box); + +protected: + bool eventFilter(QObject *object, QEvent *event); + +private: + QHash<QWidget*, ViewData*> m_views; + QHash<GestureBox*, ViewData*> m_boxes; + + GestureObserver() {} + ~GestureObserver() {} + ViewData *getViewData(QGraphicsView *view); + +private slots: + void viewDestroyed(); + +}; + +#endif // GESTUREBOX_P_H diff --git a/weather/src/loading.cpp b/weather/src/loading.cpp new file mode 100644 index 0000000..02750f9 --- /dev/null +++ b/weather/src/loading.cpp @@ -0,0 +1,77 @@ +#include "loading.h" +#include "pixmaploader.h" + +// Loading + +struct ProgressImageData +{ + const char * const name; + QPixmap pic() const { return PixmapLoader::getPic(name); } + QRectF getRect() + { + QPixmap pixmap = pic(); + return QRectF(0.0, 0.0, pixmap.width(), pixmap.height()); + } +}; + +static const int ProgressImageCount = 25; +static ProgressImageData ProgressImageArray[ProgressImageCount] = { + {"loading_1"}, + {"loading_2"}, + {"loading_3"}, + {"loading_4"}, + {"loading_5"}, + {"loading_6"}, + {"loading_7"}, + {"loading_8"}, + {"loading_9"}, + {"loading_10"}, + {"loading_11"}, + {"loading_12"}, + {"loading_13"}, + {"loading_14"}, + {"loading_15"}, + {"loading_16"}, + {"loading_17"}, + {"loading_18"}, + {"loading_19"}, + {"loading_20"}, + {"loading_21"}, + {"loading_22"}, + {"loading_23"}, + {"loading_24"}, + {"loading_25"} +}; + +void Loading::loadImages() +{ + for (int i = 0; i < ProgressImageCount; ++i) + PixmapLoader::load(ProgressImageArray[i].name); +} + +Loading::Loading(QGraphicsItem *parent) + : QGraphicsItem(parent) + , m_image(0) + , m_boundingRect(ProgressImageArray[m_image].getRect()) +{ +} + +QRectF Loading::boundingRect () const +{ + return m_boundingRect; +} + +void Loading::paint(QPainter *painter, const QStyleOptionGraphicsItem *opt, QWidget *widget) +{ + Q_UNUSED(opt); + Q_UNUSED(widget); + painter->drawPixmap(0, 0, ProgressImageArray[m_image].pic()); +} + +void Loading::timerEvent(QTimerEvent *event) +{ + Q_UNUSED(event); + ++m_image; + m_image %= ProgressImageCount; + update(); +} diff --git a/weather/src/loading.h b/weather/src/loading.h new file mode 100644 index 0000000..2735b28 --- /dev/null +++ b/weather/src/loading.h @@ -0,0 +1,33 @@ +#ifndef LOADING_H +#define LOADING_H + +#include <QObject> +#include <QGraphicsItem> +#include <QBasicTimer> +#include <QPainter> + +class Loading : public QObject, public QGraphicsItem +{ + Q_OBJECT + Q_INTERFACES(QGraphicsItem) +public: + Loading(QGraphicsItem *parent = 0); + + static void loadImages(); + + QRectF boundingRect () const; + void paint(QPainter *painter, const QStyleOptionGraphicsItem *option, QWidget *widget = 0); + + void start() { m_ticker.start(50, this); } + void stop() { m_ticker.stop(); } + +protected: + void timerEvent(QTimerEvent *event); + +private: + int m_image; + const QRectF m_boundingRect; + QBasicTimer m_ticker; +}; + +#endif // LOADING_H diff --git a/weather/src/main.cpp b/weather/src/main.cpp new file mode 100644 index 0000000..b2681a9 --- /dev/null +++ b/weather/src/main.cpp @@ -0,0 +1,84 @@ +/**************************************************************************** +** +** 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 <QApplication> +#include <QGraphicsScene> +#include <QGraphicsView> +#include <QPixmapCache> +#include <QSettings> +#include <QDebug> + +#include "system.h" +#include "mainview.h" +#include "forecastdata.h" +#include "settings.h" + +#include "forecastprovider.h" +#include "demoforecastsource.h" +#include "networkforecastsource.h" + +#if defined (Q_OS_SYMBIAN) +#include "symbiannetwork.h" +#endif + + +int main(int argc, char **argv) +{ + qRegisterMetaType<ForecastData>("ForecastData"); + + QApplication app(argc, argv); + QCoreApplication::setOrganizationName("openBossa"); + QCoreApplication::setOrganizationDomain("openbossa.org"); + QCoreApplication::setApplicationName("weather"); + +#ifdef Q_OS_SYMBIAN + const bool connected = connect(); +#else + const bool connected = true; +#endif + + if (connected) + ForecastProvider::setForecastSource(new NetworkForecastSource()); + else + ForecastProvider::setForecastSource(new DemoForecastSource(1500)); + + MainView mainView(connected); + + System::setViewMode(&mainView, System::PortraitMode); + +#if defined(Q_OS_SYMBIAN) || defined(Q_WS_MAEMO_5) + mainView.showFullScreen(); +#else + mainView.show(); +#endif + + return app.exec(); +} diff --git a/weather/src/mainview.cpp b/weather/src/mainview.cpp new file mode 100644 index 0000000..5f9b5d3 --- /dev/null +++ b/weather/src/mainview.cpp @@ -0,0 +1,170 @@ +/**************************************************************************** +** +** 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 "mainview.h" +#include "settings.h" + +#include "pixmaploader.h" + +// MainView + +MainView::MainView(bool connected, QWidget *parent) + : QGraphicsView(parent) + , m_carroussel(0) + , m_loading(0) + , m_bootManager(0) + , m_manager(0) + , m_fakeContentScreen(0) + , m_connected(connected) +{ + setScene(&m_scene); + setFrameShape(QFrame::NoFrame); + setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff); + setVerticalScrollBarPolicy(Qt::ScrollBarAlwaysOff); + setRenderHints(QPainter::Antialiasing | QPainter::SmoothPixmapTransform | + QPainter::TextAntialiasing); + setWindowTitle("Weather"); + m_scene.setSceneRect(QRectF(QPointF(0.0, 0.0), Settings::windowSize())); + resize(Settings::windowSize()); + m_scene.setBackgroundBrush(Qt::black); + + PixmapLoader::connectToOnIdleSignal(this, SLOT(pixmapLoaderIsIdle())); + Loading::loadImages(); + if (!m_connected) + FakeContentScreen::loadImages(); +} + +MainView::~MainView() +{ +} + +void MainView::pixmapLoaderIsIdle() +{ + PixmapLoader::disconnectReceiver(this); + if (m_connected) + startBoot(); + else { + m_fakeContentScreen = new FakeContentScreen(); + connect(m_fakeContentScreen, SIGNAL(userAccepted()), this, SLOT(startBoot())); + m_fakeContentScreen->setPos(0.0, 0.0); + m_scene.addItem(m_fakeContentScreen); + } +} + +void MainView::startBoot() +{ + delete m_fakeContentScreen; + m_fakeContentScreen = 0; + m_loading = new Loading(); + m_loading->setPos((geometry().width() - m_loading->boundingRect().width()) / 2, + (geometry().height() - m_loading->boundingRect().height()) / 2); + m_scene.addItem(m_loading); + m_loading->start(); + m_bootManager = new BootManager(this); + connect(m_bootManager, SIGNAL(ready()), this, SLOT(bootEnd())); + m_bootManager->run(m_connected ? Settings::getCurrentCities() : Settings::getDemoCities()); +} + +void MainView::bootEnd() +{ + m_loading->deleteLater(); + m_loading = 0; + showCarroussel(); + createCityManager(); + m_bootManager->deleteLater(); + m_bootManager = 0; +} + +void MainView::showCarroussel() +{ + QList<ForecastData> data = m_bootManager->data(); + m_carroussel = new CityCarroussel(); + connect(m_carroussel, SIGNAL(cityNameClicked()), this, SLOT(cityNameClicked())); + + connect(this, SIGNAL(moveLeft()), m_carroussel, SLOT(moveLeft())); + connect(this, SIGNAL(moveRight()), m_carroussel, SLOT(moveRight())); + + m_carroussel->update(data); + m_carroussel->setPos(0.0, 0.0); + m_carroussel->setZValue(0.0); + m_scene.addItem(m_carroussel); +} + +void MainView::createCityManager() +{ + m_manager = new CityManager(m_bootManager->data()); + m_manager->setPos(0.0, 0.0); + m_manager->setZValue(1.0); + m_manager->hide(); + +} + +void MainView::cityNameClicked() +{ + if (!m_manager) + return; + m_carroussel->setActive(false); + connect(m_manager, SIGNAL(terminated()), this, SLOT(closeCityManager())); + m_scene.addItem(m_manager); + m_manager->show(); + m_manager->showManager(m_carroussel->selected()); +} + +void MainView::closeCityManager() +{ + if (!m_manager) + return; + m_manager->hide(); + m_scene.removeItem(m_manager); + disconnect(m_manager, SIGNAL(terminated()), this, SLOT(closeCityManager())); + saveManagerList(); + m_carroussel->update(m_manager->forecastList()); + m_carroussel->setActive(true); +} + +void MainView::saveManagerList() +{ + if (m_connected) { + QStringList list; + foreach(const ForecastData &data, m_manager->forecastList()) + list.append(data.key()); + Settings::setCurrentCities(list); + } +} + +void MainView::keyPressEvent(QKeyEvent* event) +{ + if (event->key() == Qt::Key_Left) + emit moveLeft(); + if (event->key() == Qt::Key_Right) + emit moveRight(); + QGraphicsView::keyPressEvent(event); +} diff --git a/weather/src/mainview.h b/weather/src/mainview.h new file mode 100644 index 0000000..fc54837 --- /dev/null +++ b/weather/src/mainview.h @@ -0,0 +1,85 @@ +/**************************************************************************** +** +** 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 MAINVIEW_H +#define MAINVIEW_H + +#include <QGraphicsView> +#include <QKeyEvent> +#include <QGraphicsScene> + +#include "titlebar.h" +#include "citycarroussel.h" +#include "forecastdata.h" +#include "bootmanager.h" +#include "loading.h" +#include "citymanager.h" +#include "fakecontentscreen.h" + +class MainView: public QGraphicsView +{ + Q_OBJECT + +public: + MainView(bool connected, QWidget *parent = 0); + ~MainView(); + +protected: + void keyPressEvent(QKeyEvent* event); + +signals: + void moveLeft(); + void moveRight(); + +private slots: + void cityNameClicked(); + void closeCityManager(); + + void pixmapLoaderIsIdle(); + void bootEnd(); + void startBoot(); + +private: + QGraphicsScene m_scene; + CityCarroussel *m_carroussel; + Loading *m_loading; + BootManager *m_bootManager; + CityManager *m_manager; + FakeContentScreen *m_fakeContentScreen; + const bool m_connected; + + void showCarroussel(); + void createCityManager(); + void saveManagerList(); +}; + +#endif // MAINVIEW_H + diff --git a/weather/src/networkforecastsource.cpp b/weather/src/networkforecastsource.cpp new file mode 100644 index 0000000..98456c8 --- /dev/null +++ b/weather/src/networkforecastsource.cpp @@ -0,0 +1,234 @@ +#include "networkforecastsource.h" +#include "yahooweatherresponse.h" +#include "xoapweatherresponse.h" + +#include <QUrl> +#include <QNetworkRequest> +#include <QNetworkReply> +#include <QDebug> +#include <QBuffer> +#include <QDomDocument> +#include <QDateTime> +#include <QTime> + +#define CITY_NAME_HOST "xoap.weather.com" +#define CITY_NAME_TO_LOCATION_ID_URL "http://" CITY_NAME_HOST "/search/search" +#define CITY_NAME_TO_LOCATION_ID_PARAM "where" + +#define FORECAST_HOST "weather.yahooapis.com" +#define FORECAST_URL "http://" FORECAST_HOST "/forecastrss" +#define FORECAST_LOCATION_PARAM "p" +#define FORECAST_UNIT_PARAM "u" +#define FORECAST_CELSIUS_UNIT "c" + +#define YWEATHER_NS_URI "http://xml.weather.yahoo.com/ns/rss/1.0" +#define YWEATHER_NS_NAME "yweather" + +static int getId() +{ + static int id(0); + return ++id; +} + +// LocationRequestManager + +LocationRequestManager::LocationRequestManager(QObject *parent) + : QObject(parent) +{ + connect(&m_network, SIGNAL(finished(QNetworkReply*)), + this, SLOT(receiveResponse(QNetworkReply*))); +} + +int LocationRequestManager::addRequest(const QString &query) +{ + if (m_requests.contains(query)) + return m_requests[query]; + QUrl url(CITY_NAME_TO_LOCATION_ID_URL); + url.addQueryItem(CITY_NAME_TO_LOCATION_ID_PARAM, query); + m_network.get(QNetworkRequest(url)); + + int result = getId(); + m_requests[query] = result; + return result; +} + +QString LocationRequestManager::readResponse(const QString &query, QNetworkReply *reply) +{ + + if (reply->error() != QNetworkReply::NoError) { + qWarning() << "location id query error: " << query; + return QString(); + } + + QDomDocument doc; + + if (!doc.setContent(reply)) { + qWarning() << "location id parse error: " << query; + return QString(); + } + XoapWeatherResponse reader; + reader.read(doc.documentElement()); + + if (reader.items().count() == 0) { + qWarning() << "empty location id response: " << query; + return QString(); + } + + return reader.items()[0].id(); + +} + +void LocationRequestManager::receiveResponse(QNetworkReply *reply) +{ + const QString query = reply->request().url().queryItemValue(CITY_NAME_TO_LOCATION_ID_PARAM); + + if (query.isNull() || !m_requests.contains(query)) { + qWarning() << "Unexpected location id query response. (query = " << query << ")"; + return; + } + const int reqId = m_requests[query]; + m_requests.remove(query); + + QString locId = readResponse(query, reply); + + if (locId.isEmpty()) + emit locationIdQueryError(reqId, query); + else + emit newLocationId(reqId, locId); +} + +// ForecastRequestmanager + +ForecastRequestmanager::ForecastRequestmanager(QObject *parent) + : QObject(parent) +{ + connect(&m_network, SIGNAL(finished(QNetworkReply*)), + this, SLOT(receiveResponse(QNetworkReply*))); +} + +int ForecastRequestmanager::addRequest(const QString &locId) +{ + if (m_requests.contains(locId)) + return m_requests[locId][0]; + int result = getId(); + doAddRequest(result, locId); + return result; +} + +void ForecastRequestmanager::addRequest(int reqId, const QString &locId) +{ + if (m_requests.contains(locId)) { + m_requests[locId].append(reqId); + return; + } + doAddRequest(reqId, locId); +} + +void ForecastRequestmanager::doAddRequest(int reqId, const QString &locId) +{ + QUrl url(FORECAST_URL); + url.addQueryItem(FORECAST_LOCATION_PARAM, locId); + url.addQueryItem(FORECAST_UNIT_PARAM, FORECAST_CELSIUS_UNIT); + m_network.get(QNetworkRequest (url)); + QList<int> list; + list.append(reqId); + m_requests[locId] = list; +} + +YahooWeatherResponse *ForecastRequestmanager::readResponse(const QString &locId, + QNetworkReply *reply) +{ + if (reply->error() != QNetworkReply::NoError) { + qWarning() << "forecast query error: " << locId; + return 0; + } + + QDomDocument doc; + + if (!doc.setContent(reply)) { + qWarning() << "forecast parse error: " << locId; + return 0; + } + + bool ok = doc.documentElement().childNodes().count() > 0 + && doc.documentElement().childNodes().at(0).isElement(); + if (ok) { + QDomElement element = doc.documentElement().childNodes().at(0).toElement(); + ok = element.elementsByTagName("lastBuildDate").count() == 1; + } + + if (!ok) { + qWarning() << "invalid forecast response: " << locId; + return 0; + } + + return new YahooWeatherResponse(locId, doc.documentElement()); +} + +void ForecastRequestmanager::receiveResponse(QNetworkReply *reply) +{ + const QString locId = reply->request().url().queryItemValue(FORECAST_LOCATION_PARAM); + + if (locId.isNull() || !m_requests.contains(locId)) { + qWarning() << "Unexpected forecast query response. (locId = " << locId << ")"; + return; + } + + const QList<int> list = m_requests[locId]; + m_requests.remove(locId); + + YahooWeatherResponse *result = readResponse(locId, reply); + if (result) + foreach (int reqId, list) { + emit newForecastResponse(reqId, result); + } + else + foreach (int reqId, list) + emit forecastResponseError(reqId); +} + +// NetworkForecastSource + +NetworkForecastSource::NetworkForecastSource(QObject *parent) + : ForecastSource(parent) +{ + connect(&m_locationManager, SIGNAL(newLocationId(int, QString)), + &m_forecastManager, SLOT(addRequest(int, QString))); + + connect(&m_locationManager, SIGNAL(locationIdQueryError(int, QString)), + &m_forecastManager, SLOT(addRequest(int, QString))); + + connect(&m_forecastManager, SIGNAL(forecastResponseError(int)), + this, SLOT(forecastResponseError(int))); + + connect(&m_forecastManager, SIGNAL(newForecastResponse(int, YahooWeatherResponse*)), + this, SLOT(newForecastResponse(int, YahooWeatherResponse*))); +} + +NetworkForecastSource::~NetworkForecastSource() +{ +} + +int NetworkForecastSource::getForecast(const QString &key, bool locationId) +{ + if (locationId) + return m_forecastManager.addRequest(key); + else + return m_locationManager.addRequest(key); +} + +void NetworkForecastSource::newForecastResponse(int reqId, YahooWeatherResponse *forecast) +{ + forecast->print(); + emit forecastReceived(reqId, ForecastData(forecast)); +} + +void NetworkForecastSource::forecastResponseError(int reqId) +{ + emit forecastReceived(reqId, ForecastData(0)); +} + + + + + diff --git a/weather/src/networkforecastsource.h b/weather/src/networkforecastsource.h new file mode 100644 index 0000000..49f26c5 --- /dev/null +++ b/weather/src/networkforecastsource.h @@ -0,0 +1,75 @@ +#ifndef NETWORKFORECASTSOURCE_H +#define NETWORKFORECASTSOURCE_H + +#include "forecastdata.h" +#include "forecastsource.h" +#include <QObject> +#include <QMap> +#include <QStringList> +#include <QNetworkAccessManager> + +class LocationRequestManager : public QObject +{ + Q_OBJECT +public: + LocationRequestManager(QObject *parent = 0); + int addRequest(const QString &query); + +signals: + void newLocationId(int reqId, const QString &locId); + void locationIdQueryError(int reqId, const QString &query); + +private slots: + void receiveResponse(QNetworkReply *reply); + +private: + QMap<QString, int> m_requests; + QNetworkAccessManager m_network; + + QString readResponse(const QString &query, QNetworkReply *reply); +}; + +class ForecastRequestmanager : public QObject +{ + Q_OBJECT +public: + ForecastRequestmanager(QObject *parent = 0); + + int addRequest(const QString &locId); + +public slots: + void addRequest(int reqId, const QString &locId); + +signals: + void newForecastResponse(int reqId, YahooWeatherResponse *forecast); + void forecastResponseError(int reqId); + +private slots: + void receiveResponse(QNetworkReply *reply); + +private: + QMap<QString, QList<int> > m_requests; + QNetworkAccessManager m_network; + + YahooWeatherResponse *readResponse(const QString &query, QNetworkReply *locId); + void doAddRequest(int reqId, const QString &locId); +}; + +class NetworkForecastSource : public ForecastSource +{ + Q_OBJECT +public: + NetworkForecastSource(QObject *parent = 0); + ~NetworkForecastSource(); + int getForecast(const QString &key, bool locationId); + +private: + LocationRequestManager m_locationManager; + ForecastRequestmanager m_forecastManager; + +private slots: + void newForecastResponse(int reqId, YahooWeatherResponse *forecast); + void forecastResponseError(int reqId); +}; + +#endif // NETWORKFORECASTSOURCE_H diff --git a/weather/src/painttextitem.cpp b/weather/src/painttextitem.cpp new file mode 100644 index 0000000..7356815 --- /dev/null +++ b/weather/src/painttextitem.cpp @@ -0,0 +1,67 @@ +#include "painttextitem.h" +#include <QPainter> +#include <QDebug> + +TextPainter::TextPainter(int fontSize, QColor color, const QString &text) + : m_pen(color) + , m_brush(color) + , m_text(text) + , m_quoted(false) + , m_maxWidth(-1) +{ + m_font.setFamily("Nokia Sans"); + m_font.setPixelSize(fontSize); + m_font.setStyleStrategy(QFont::PreferAntialias); + m_pen.setJoinStyle(Qt::RoundJoin); +} + +int TextPainter::width() const +{ + QFontMetrics m(m_font); + int result = 0; + if (m_quoted) + result += 2 * m.width('"'); + QString text = m_maxWidth > 0 ? m.elidedText(m_text, Qt::ElideRight, m_maxWidth - result) + : m_text; + return result + m.width(text); +} + +void TextPainter::paint(QPainter *painter) +{ + QFontMetrics m(m_font); + QString text; + if (m_quoted) { + int quote = 2 * m.width('"'); + text = m_maxWidth > 0 ? m.elidedText(m_text, Qt::ElideRight, m_maxWidth - quote) : m_text; + text = '"' + text + '"'; + } else + text = m_maxWidth > 0 ? m.elidedText(m_text, Qt::ElideRight, m_maxWidth) : m_text; + painter->setFont(m_font); + painter->setPen(m_pen); + painter->setBrush(m_brush); + + painter->drawText(QPointF(m_pos.x(), m_pos.y() + m.ascent()), text); +} + +void TextPainter::locateAtCenter(TextPainter *item, qreal left, qreal top, qreal width) +{ + item->setPos(left + (width - item->width()) / 2, top); +} + +void TextPainter::locateAtCenter(QList<TextPainter*> items, qreal left, qreal top, qreal width) +{ + static const int margin = 5; + + if (items.isEmpty()) + return; + + int itemsWidth = (items.count() - 1) * margin; + for (int i = 0; i < items.count(); ++i) + itemsWidth += items[i]->width(); + + left += (width - itemsWidth) / 2; + for (int i = 0; i < items.count(); ++i) { + items[i]->setPos(left, top); + left += items[i]->width() + margin; + } +} diff --git a/weather/src/painttextitem.h b/weather/src/painttextitem.h new file mode 100644 index 0000000..9811226 --- /dev/null +++ b/weather/src/painttextitem.h @@ -0,0 +1,49 @@ +#ifndef PAINTTEXTITEM_H +#define PAINTTEXTITEM_H + +#include <QFont> +#include <QPen> +#include <QBrush> +#include <QFontMetrics> +#include <QGraphicsItem> + +class TextPainter +{ +public: + TextPainter(int fontSize, QColor color, const QString &text); + QFont &font() { return m_font; } + QPen &pen() { return m_pen; } + QBrush &brush() { return m_brush; } + + const QString text() const { return m_text; } + void setText(const QString &text) { m_text = text; } + + QPointF pos() const { return m_pos; } + void setPos(QPointF pos) { m_pos = pos; } + void setPos(qreal x, qreal y) { setPos(QPointF(x, y)); } + + bool quoted() const { return m_quoted; } + void setQuoted(bool quoted) { m_quoted = quoted; } + + int maxWidth() const { return m_maxWidth; } + void setMaxWidth(int width) { m_maxWidth = width; } + + int width() const; + int height() const { return QFontMetrics(m_font).height(); } + + void paint(QPainter *painter); + + static void locateAtCenter(QList<TextPainter*> items, qreal left, qreal top, qreal width); + static void locateAtCenter(TextPainter *item, qreal left, qreal top, qreal width); + +private: + QFont m_font; + QPen m_pen; + QBrush m_brush; + QString m_text; + QPointF m_pos; + bool m_quoted; + int m_maxWidth; +}; + +#endif // PAINTTEXTITEM_H diff --git a/weather/src/pixmapbutton.cpp b/weather/src/pixmapbutton.cpp new file mode 100644 index 0000000..6ec8be5 --- /dev/null +++ b/weather/src/pixmapbutton.cpp @@ -0,0 +1,30 @@ +#include "pixmapbutton.h" + +// PixmapButton + +PixmapButton::PixmapButton(qreal minSize, const QPixmap &pixmap, QGraphicsItem *parent) + : QGraphicsPixmapItem(pixmap, parent) + , m_minSize(minSize) +{ + setShapeMode(BoundingRectShape); +} + +void PixmapButton::mousePressEvent(QGraphicsSceneMouseEvent *event) +{ + Q_UNUSED(event); + emit clicked(); +} + +QPainterPath PixmapButton::shape() const +{ + return QGraphicsItem::shape(); +} + +QRectF PixmapButton::boundingRect() const +{ + QRectF result(QGraphicsPixmapItem::boundingRect()); + qreal hMargin = m_minSize < result.width() ? 0 : (m_minSize - result.width()) / 2; + qreal vMargin = m_minSize < result.height() ? 0 : (m_minSize - result.height()) / 2; + result.adjust(-hMargin, -vMargin, hMargin, vMargin); + return result; +} diff --git a/weather/src/pixmapbutton.h b/weather/src/pixmapbutton.h new file mode 100644 index 0000000..60c95a3 --- /dev/null +++ b/weather/src/pixmapbutton.h @@ -0,0 +1,29 @@ +#ifndef PIXMAPBUTTON_H +#define PIXMAPBUTTON_H + +#include <QObject> +#include <QGraphicsPixmapItem> + +class PixmapButton: public QObject, public QGraphicsPixmapItem +{ + Q_OBJECT + Q_PROPERTY(qreal opacity READ opacity WRITE setOpacity); +public: + PixmapButton(qreal minSize, const QPixmap &pixmap, QGraphicsItem *parent = 0); + QRectF boundingRect() const; + QPainterPath shape() const; + +public slots: + void doHide() { hide(); } + +signals: + void clicked(); + +protected: + void mousePressEvent(QGraphicsSceneMouseEvent *event); + +private: + const qreal m_minSize; +}; + +#endif // PIXMAPBUTTON_H diff --git a/weather/src/pixmaploader.cpp b/weather/src/pixmaploader.cpp new file mode 100644 index 0000000..e60cef4 --- /dev/null +++ b/weather/src/pixmaploader.cpp @@ -0,0 +1,125 @@ +#include "pixmaploader.h" +#include "settings.h" +#include <QMutexLocker> + +#include "settings.h" + +// PixmapLoaderThread + +PixmapLoaderThread::PixmapLoaderThread(PixmapLoader *loader) + : QThread(loader) + , m_loader(loader) +{ +} + +void PixmapLoaderThread::run() +{ + static const qreal widthFactor = Settings::widthFactor(); + static const qreal heightFactor = Settings::heightFactor(); + QString name = m_loader->dequeue(); + while (!name.isEmpty()) { + QImage image(":images/weather_elements/" + name + ".png"); + if (!image.isNull()) { + QSize size(widthFactor * image.size().width(), heightFactor * image.size().height()); + if (size.width() && size.height()) + image = image.scaled(size, Qt::IgnoreAspectRatio, Qt::SmoothTransformation); + } + emit imageIsReady(name, image); + name = m_loader->dequeue(); + } + deleteLater(); +} + +// PixmapLoader + +PixmapLoader::PixmapLoader(QObject *parent) + : QObject(parent) + , m_thread(0) +{ +} + +void PixmapLoader::load(const QString &name) +{ + instance()->enqueue(name); +} + +PixmapLoader *PixmapLoader::instance() +{ + static PixmapLoader * const result(new PixmapLoader()); + return result; +} + +void PixmapLoader::connectToOnIdleSignal(QObject *receiver, const char *method) +{ + QObject::connect(instance(), SIGNAL(onIdle()), receiver, method, Qt::QueuedConnection); +} + +void PixmapLoader::disconnectReceiver(QObject *receiver) +{ + instance()->disconnect(receiver); +} + +QPixmap PixmapLoader::getPic(const QString &name) +{ + PixmapLoader *obj = instance(); + QMutexLocker locker(&obj->m_mutex); + return obj->m_store.contains(name) ? obj->m_store[name] : QPixmap(); +} + +void PixmapLoader::enqueue(const QString &name) +{ + QMutexLocker locker(&m_mutex); + + if (m_queue.indexOf(name) >= 0 || m_currentImages.indexOf(name) >= 0 || m_store.contains(name)) + return; + + m_queue.append(name); + + if (!m_thread) { + m_thread = new PixmapLoaderThread(this); + connect(m_thread, SIGNAL(imageIsReady(QString,QImage)), + this, SLOT(imageIsReady(QString,QImage))); + m_thread->start(); + } + m_condition.wakeOne(); +} + +QString PixmapLoader::doDequeue() +{ + QString name = m_queue.isEmpty() ? QString() : m_queue.takeAt(0); + + while (!name.isEmpty() && (m_currentImages.indexOf(name) >= 0 || m_store.contains(name))) + name = m_queue.isEmpty() ? QString() : m_queue.takeAt(0); + + if (!name.isEmpty()) + m_currentImages.append(name); + + return name; +} + +QString PixmapLoader::dequeue() +{ + static const int waitTime = 60000; + QMutexLocker locker(&m_mutex); + QString result; + do + result = doDequeue(); + while (result.isEmpty() && m_condition.wait(&m_mutex, waitTime)); + if (result.isEmpty()) { + m_thread = 0; + } + return result; +} + +void PixmapLoader::imageIsReady(const QString &name, QImage image) +{ + QMutexLocker locker(&m_mutex); + + QPixmap pixmap = QPixmap::fromImage(image, Qt::ColorOnly); + if (!pixmap.isNull()) + m_store[name] = pixmap; + + m_currentImages.removeAll(name); + if (m_currentImages.isEmpty() && m_queue.isEmpty()) + emit onIdle(); +} diff --git a/weather/src/pixmaploader.h b/weather/src/pixmaploader.h new file mode 100644 index 0000000..f32bf64 --- /dev/null +++ b/weather/src/pixmaploader.h @@ -0,0 +1,60 @@ +#ifndef PIXMAPLOADER_H +#define PIXMAPLOADER_H + +#include <QThread> +#include <QMutex> +#include <QMap> +#include <QStringList> +#include <QPixmap> +#include <QWaitCondition> + +class PixmapLoader; + +class PixmapLoaderThread : public QThread +{ + Q_OBJECT +public: + PixmapLoaderThread(PixmapLoader *loader); +signals: + void imageIsReady(const QString &name, QImage image); +protected: + void run(); +private: + PixmapLoader * const m_loader; + +}; + +class PixmapLoader : public QObject +{ + Q_OBJECT +public: + static void load(const QString &name); + static void connectToOnIdleSignal(QObject *receiver, const char *method); + static void disconnectReceiver(QObject *receiver); + static QPixmap getPic(const QString &name); + +signals: + void onIdle(); + +private: + friend class PixmapLoaderThread; + QMutex m_mutex; + QWaitCondition m_condition; + + QStringList m_queue; + QStringList m_currentImages; + QMap<QString, QPixmap> m_store; + PixmapLoaderThread *m_thread; + + PixmapLoader(QObject *parent = 0); + static PixmapLoader *instance(); + + void enqueue(const QString &name); + QString doDequeue(); + QString dequeue(); + +private slots: + void imageIsReady(const QString &name, QImage image); +}; + +#endif // PIXMAPLOADER_H diff --git a/weather/src/scrollbar.cpp b/weather/src/scrollbar.cpp new file mode 100644 index 0000000..5fc1264 --- /dev/null +++ b/weather/src/scrollbar.cpp @@ -0,0 +1,100 @@ +/**************************************************************************** +** +** 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 <QPropertyAnimation> +#include <QDebug> + +#include "scrollbar.h" +#include "pixmaploader.h" + +ScrollBar::ScrollBar(QGraphicsItem* parent) + : QGraphicsPixmapItem(PixmapLoader::getPic("scroll"), parent) + , m_knob(PixmapLoader::getPic("scroll_knob"), this) + , m_height(pixmap().height() - m_knob.pixmap().height()) + , m_value(0.0) + , m_visible(false) +{ + hide(); + setOpacity(0.0); + m_knob.setPos(0.0, 0.0); + m_timer.setSingleShot(true); + m_timer.setInterval(1000); + + connect(&m_timer, SIGNAL(timeout()), this, SLOT(timeout())); +} + +int ScrollBar::loadImages() +{ + PixmapLoader::load("scroll"); + PixmapLoader::load("scroll_knob"); + return 2; +} +bool ScrollBar::startAnimation(bool show) +{ + if (show == m_visible) + return false; + + m_visible = show; + m_timer.stop(); + + if (m_animation) + m_animation->stop(); + + QPropertyAnimation* animation = new QPropertyAnimation(this, "opacity"); + animation->setEasingCurve(QEasingCurve::OutExpo); + animation->setDuration(500); + animation->setEndValue(show ? 1.0 : 0.0); + + if (show) + connect(animation, SIGNAL(finished()), &m_timer, SLOT(start())); + else + connect(animation, SIGNAL(finished()), this, SLOT(hideScrollBar())); + + m_animation = animation; + animation->start(QAbstractAnimation::DeleteWhenStopped); + return true; +} + +void ScrollBar::setValue(qreal value) +{ + if (value < 0 || value > 1.0) + return; + m_value = value; + m_knob.setPos(0, m_value * m_height); + + show(); + if (!startAnimation(true)) + m_timer.start(); +} + + + + diff --git a/weather/src/scrollbar.h b/weather/src/scrollbar.h new file mode 100644 index 0000000..fb509d7 --- /dev/null +++ b/weather/src/scrollbar.h @@ -0,0 +1,65 @@ +/**************************************************************************** +** +** 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 SCROLLBAR_H +#define SCROLLBAR_H + +#include <QTimer> +#include <QPointer> +#include <QGraphicsPixmapItem> +#include <QAbstractAnimation> + +class ScrollBar: public QObject, public QGraphicsPixmapItem +{ + Q_OBJECT + Q_PROPERTY(qreal opacity READ opacity WRITE setOpacity); +public: + ScrollBar(QGraphicsItem* parent = 0); + qreal value() const { return m_value; } + void setValue(qreal); + static int loadImages(); + +private slots: + void timeout() { startAnimation(false); } + void hideScrollBar() { hide(); } + +private: + QGraphicsPixmapItem m_knob; + const qreal m_height; + qreal m_value; + QTimer m_timer; + QPointer<QAbstractAnimation> m_animation; + bool m_visible; + + bool startAnimation(bool show); +}; + +#endif /* SCROLLBAR_H */ diff --git a/weather/src/settings.cpp b/weather/src/settings.cpp new file mode 100644 index 0000000..32b3071 --- /dev/null +++ b/weather/src/settings.cpp @@ -0,0 +1,122 @@ +/**************************************************************************** +** +** 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 "settings.h" +#include <QApplication> +#include <QDesktopWidget> +#include <QTimer> +#include <QDebug> + +#if defined(Q_OS_SYMBIAN) +#define SETTINGS_FILE "c://data/weather.ini" +#elif defined(Q_WS_MAEMO_5) +#define SETTINGS_FILE "/usr/share/applications/weather.ini" +#else +#define SETTINGS_FILE "weather.ini" +#endif + +Settings::Settings() + : m_settings(SETTINGS_FILE, QSettings::IniFormat) +{ +} + +Settings *Settings::instance() +{ + static Settings * const result = new Settings(); + return result; +} + +QSize Settings::windowSize() +{ +#ifdef Q_OS_SYMBIAN + static const QSize result(QApplication::desktop()->screenGeometry().size()); +#else + static const QSize result(instance()->m_settings.value("windowSize").toSize()); +#endif + return result; +} + +bool Settings::scaledImages() +{ + QSize size(windowSize()); + return size.width() != 480 || size.height() != 864; +} + +class CitySortHelper +{ +public: + CitySortHelper(const QMap<QString, int> &map) : m_map(map) {} + bool operator()(const QString &city1, const QString &city2) + { + return m_map[city1] < m_map[city2]; + } + +private: + const QMap<QString, int> &m_map; +}; + +QStringList Settings::getCurrentCities() +{ + return getCities("Cities"); +} + +QStringList Settings::getDemoCities() +{ + return getCities("Demo"); +} + +QStringList Settings::getCities(const QString &type) +{ + Settings *obj = instance(); + + obj->m_settings.beginGroup(type); + + QStringList result = obj->m_settings.allKeys(); + QMap<QString, int> map; + foreach(const QString &key, result) + map[key] = obj->m_settings.value(key).toInt(); + + obj->m_settings.endGroup(); + + qSort(result.begin(), result.end(), CitySortHelper(map)); + return result; +} + +void Settings::setCurrentCities(QStringList cities) +{ + Settings *obj = instance(); + obj->m_settings.beginGroup("Cities"); + obj->m_settings.remove(""); + for (int i = 0; i < cities.count(); ++i) + obj->m_settings.setValue(cities[i], i); + obj->m_settings.endGroup(); + obj->m_settings.sync(); +} diff --git a/weather/src/settings.h b/weather/src/settings.h new file mode 100644 index 0000000..7556e72 --- /dev/null +++ b/weather/src/settings.h @@ -0,0 +1,73 @@ +/**************************************************************************** +** +** 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 SETTINGS_H +#define SETTINGS_H + +#include <QObject> +#include <QSettings> +#include <QSize> +#include <QPixmap> + +class Settings : public QObject +{ + Q_OBJECT +public: + Settings(); + + static QString elementPath(const QString & element) + { return ":images/weather_elements/" + element; } + + static QSize windowSize(); + static bool scaledImages(); + + static qreal referenceWidth() { return 480.0; } + static qreal referenceHeight() { return 864.0; } + + static qreal widthFactor() { return qreal(windowSize().width()) / referenceWidth(); } + static qreal heightFactor() { return qreal(windowSize().height()) / referenceHeight(); } + + static qreal scaleWidth(qreal width) { return width * widthFactor(); } + static qreal scaleHeight(qreal height) { return height * heightFactor(); } + + static QStringList getDemoCities(); + static QStringList getCurrentCities(); + static void setCurrentCities(QStringList cities); + +private: + QSettings m_settings; + static Settings *instance(); + + static QStringList getCities(const QString &type); + +}; + +#endif // SETTINGS_H diff --git a/weather/src/symbiannetwork.h b/weather/src/symbiannetwork.h new file mode 100644 index 0000000..52cf45c --- /dev/null +++ b/weather/src/symbiannetwork.h @@ -0,0 +1,156 @@ +/**************************************************************************** +** +** Copyright (C) 2009 Nokia Corporation and/or its subsidiary(-ies). +** Contact: Qt Software Information (qt-info@nokia.com) +** +** This file is part of the examples of the Qt Toolkit. +** +** $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 either Technology Preview License Agreement or the +** Beta Release License Agreement. +** +** 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.0, included in the file LGPL_EXCEPTION.txt in this +** package. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3.0 as published by the Free Software +** Foundation and appearing in the file LICENSE.GPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU General Public License version 3.0 requirements will be +** met: http://www.gnu.org/copyleft/gpl.html. +** +** If you are unsure which license is appropriate for your use, please +** contact the sales department at qt-sales@nokia.com. +** $QT_END_LICENSE$ +** +****************************************************************************/ +#ifndef SYMBIAN_NETWORK_H +#define SYMBIAN_NETWORK_H + +#include <es_sock.h> +#include <sys/socket.h> +#include <net/if.h> +#include <rconnmon.h> + + +#include <stdarg.h> +#include <e32std.h> + +/* +void writeDebugMsg(const char* msg, ...) +{ + static char buffer[1024]; + + va_list args; + va_start( args, msg); + vsprintf(buffer, msg, args); + va_end( args); + + FILE *logfile; + logfile = fopen("C:\\Data\\weather.log", "a"); + fprintf(logfile, "%s\n", buffer); + fclose(logfile); +} +*/ + +QString qt_TDesC2QStringL(const TDesC& aDescriptor) +{ +#ifdef QT_NO_UNICODE + return QString::fromLocal8Bit(aDescriptor.Ptr(), aDescriptor.Length()); +#else + return QString::fromUtf16(aDescriptor.Ptr(), aDescriptor.Length()); +#endif +} + +static bool useCurrentConnectionL(RConnectionMonitor &monitor) +{ + TUint count; + TRequestStatus status; + monitor.GetConnectionCount(count, status); + User::WaitForRequest(status); + User::LeaveIfError(status.Int()); + if (count <= 0) + return false; + + TUint connId; + TUint subConnCount; + User::LeaveIfError(monitor.GetConnectionInfo(1, connId, subConnCount)); + TBuf<50> iapName; + monitor.GetStringAttribute(connId, 0, KIAPName, iapName, status); + User::WaitForRequest(status); + User::LeaveIfError(status.Int()); + + QString strIapName = qt_TDesC2QStringL(iapName); + struct ifreq ifReq; + strcpy(ifReq.ifr_name, strIapName.toLatin1().data()); + User::LeaveIfError(setdefaultif(&ifReq)); + return true; +} + +static bool useCurrentConnectionL() +{ + RConnectionMonitor monitor; + if (monitor.ConnectL() != KErrNone) + return false; + CleanupClosePushL(monitor); + bool result = useCurrentConnectionL(monitor); + CleanupStack::PopAndDestroy(); + return result; +} + +static bool useCurrentConnection() +{ + bool result = false; + TRAPD(error, result = useCurrentConnectionL()); + return error == KErrNone && result; +} + +static void createNewConnectionL() +{ + RSocketServ server; + User::LeaveIfError(server.Connect()); + CleanupClosePushL(server); + + RConnection connection; + User::LeaveIfError(connection.Open(server)); + CleanupClosePushL(connection); + User::LeaveIfError(connection.Start()); + + _LIT(KIapName, "IAP\\Name"); + TBuf8<50> iapName; + User::LeaveIfError(connection.GetDesSetting(TPtrC(KIapName), iapName)); + connection.Stop(); + CleanupStack::PopAndDestroy(2); + + iapName.ZeroTerminate(); + struct ifreq ifReq; + strcpy(ifReq.ifr_name, (char*)iapName.Ptr()); + User::LeaveIfError(setdefaultif(&ifReq)); +} + +static bool createNewConnection() +{ + TRAPD(error, createNewConnectionL()); + return error == KErrNone; +} + +static bool connect() +{ + return useCurrentConnection() || createNewConnection(); +} + +#endif diff --git a/weather/src/titlebar.cpp b/weather/src/titlebar.cpp new file mode 100644 index 0000000..c38377a --- /dev/null +++ b/weather/src/titlebar.cpp @@ -0,0 +1,75 @@ +/**************************************************************************** +** +** 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 "titlebar.h" +#include "settings.h" +#include "pixmaploader.h" + +#include <QFont> +#include <QCoreApplication> + +// CloseButton + +CloseButton::CloseButton(QGraphicsItem *parent) + : PixmapButton(80.0, PixmapLoader::getPic("button_close"), parent) +{ +} + +void CloseButton::mousePressEvent(QGraphicsSceneMouseEvent *event) +{ + Q_UNUSED(event); + QCoreApplication::instance()->quit(); +} + +TitleBar::TitleBar(QGraphicsItem *parent) + : QGraphicsPixmapItem(PixmapLoader::getPic("title_bar"), parent) +{ + QGraphicsTextItem *text = new QGraphicsTextItem(this); + QFont font = text->font(); + font.setFamily("Nokia sans regular"); + font.setPixelSize(Settings::scaleHeight(25)); + text->setFont(font); + text->setDefaultTextColor(QColor("white")); + text->setPlainText("Weather"); + QRectF rect(boundingRect()); + qreal x = rect.left() + (rect.width() - text->boundingRect().width()) / 2; + text->setPos(x, Settings::scaleHeight(5.0)); + + CloseButton *button = new CloseButton(this); + button->setPos(Settings::scaleWidth(442.0), Settings::scaleHeight(14.0)); +} + +int TitleBar::loadImages() +{ + PixmapLoader::load("button_close"); + PixmapLoader::load("title_bar"); + return 2; +} diff --git a/weather/src/titlebar.h b/weather/src/titlebar.h new file mode 100644 index 0000000..4636a77 --- /dev/null +++ b/weather/src/titlebar.h @@ -0,0 +1,53 @@ +/**************************************************************************** +** +** 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 TITLEBAR_H +#define TITLEBAR_H + +#include <QGraphicsPixmapItem> +#include "pixmapbutton.h" + +class CloseButton: public PixmapButton +{ +public: + CloseButton(QGraphicsItem *parent = 0); +protected: + void mousePressEvent(QGraphicsSceneMouseEvent *event); +}; + +class TitleBar : public QGraphicsPixmapItem +{ +public: + TitleBar(QGraphicsItem *parent = 0); + static int loadImages(); +}; + +#endif // TITLEBAR_H diff --git a/weather/src/xoapweatherresponse.cpp b/weather/src/xoapweatherresponse.cpp new file mode 100644 index 0000000..5804bc9 --- /dev/null +++ b/weather/src/xoapweatherresponse.cpp @@ -0,0 +1,50 @@ +#include "xoapweatherresponse.h" + +#include <QDebug> + +static inline int strToInt(const QString &str, int defaultValue) +{ + bool ok; + int result = str.toInt(&ok); + return ok ? result : defaultValue; +} + +static inline QString getNodeContentText(QDomNode node) +{ + if (node.childNodes().count() == 0 || !node.childNodes().at(0).isText()) + return QString(); + return node.childNodes().at(0).nodeValue(); +} + +void XoapWeatherResponse::Item::read(QDomElement element) +{ + m_id = element.attribute("id"); + m_type = strToInt(element.attribute("type"), 0); + m_texts.clear(); + m_texts << getNodeContentText(element).split(",", QString::SkipEmptyParts); +} + +XoapWeatherResponse::XoapWeatherResponse() +{ +} + +void XoapWeatherResponse::read(QDomElement element) +{ + m_version = element.attribute("ver"); + for (int i = 0; i < element.childNodes().count(); ++i) { + if (element.childNodes().at(i).isElement()) { + Item item; + item.read(element.childNodes().at(i).toElement()); + m_list.append(item); + } + } +} + +void XoapWeatherResponse::print() +{ + /* + qDebug() << m_version << m_list.count() << " items"; + for (int i = 0; i < m_list.count(); ++i) + qDebug() << "item " << i << " : " << m_list[i].id() << m_list[i].type() << m_list[i].texts(); + */ +} diff --git a/weather/src/xoapweatherresponse.h b/weather/src/xoapweatherresponse.h new file mode 100644 index 0000000..1fd13cd --- /dev/null +++ b/weather/src/xoapweatherresponse.h @@ -0,0 +1,43 @@ +#ifndef XOAPWEATHERRESPONSE_H +#define XOAPWEATHERRESPONSE_H + +#include <QString> +#include <QStringList> +#include <QDomElement> +#include <QTime> +#include <QDate> +#include <QDateTime> + +class XoapWeatherResponse +{ +public: + class Item + { + public: + Item() : m_type(0) {} + void read(QDomElement element); + + QStringList texts() const { return m_texts; } + int type() const { return m_type; } + QString id() const { return m_id; } + + private: + QStringList m_texts; + int m_type; + QString m_id; + + }; + XoapWeatherResponse(); + void read(QDomElement element); + void print(); + + QString versionid() const { return m_version; } + const QList<Item> &items() const { return m_list; } + + +private: + QString m_version; + QList<Item> m_list; +}; + +#endif // XOAPWEATHERRESPONSE_H diff --git a/weather/src/yahooweatherresponse.cpp b/weather/src/yahooweatherresponse.cpp new file mode 100644 index 0000000..652d2c8 --- /dev/null +++ b/weather/src/yahooweatherresponse.cpp @@ -0,0 +1,165 @@ +#include "yahooweatherresponse.h" +#include <QDebug> + +static inline int strToInt(const QString &str, int defaultValue) +{ + bool ok; + int result = str.toInt(&ok); + return ok ? result : defaultValue; +} + +static inline float strToFloat(const QString &str, qreal defaultValue) +{ + bool ok; + float result = str.toFloat(&ok); + return ok ? result : defaultValue; +} + +static inline YahooWeatherResponse::PressureState readPressure(const QString &str) +{ + switch (strToInt(str, 0)) { + case 1: return YahooWeatherResponse::Rising; + case 2: return YahooWeatherResponse::Falling; + default: return YahooWeatherResponse::Steady; + } +} + +static inline QString getNodeContentText(QDomNode node) +{ + if (node.childNodes().count() == 0 || !node.childNodes().at(0).isText()) + return QString(); + return node.childNodes().at(0).nodeValue(); +} + +static const QDateTime readDateTime(const QString &str) +{ + QString text = str.right(str.length() - str.indexOf(',') - 1); + int len = text.indexOf("am", 15); + len = len < 0 ? text.indexOf("pm", 15) : len; + text = text.left(len + 2).trimmed(); + return QDateTime::fromString(text, "dd MMM yyyy h:mm ap"); +} + +YahooWeatherResponse::Location::Location(QDomElement element) + : m_city(element.attribute("city")) + , m_region(element.attribute("region")) + , m_country(element.attribute("country")) +{} + +YahooWeatherResponse::Units::Units(QDomElement element) + : m_temperature(element.attribute("temperature") == "f" ? Fahrenheit : Celsius) + , m_distance(element.attribute("distance") == "mi" ? Miles : Kilometers) + , m_pressure(element.attribute("pressure") == "in" ? PoundsPerSquare : Millibars) + , m_speed(element.attribute("speed") == "mph" ? MilesPerHour : KilometersPerHour) +{} + +YahooWeatherResponse::Wind::Wind(QDomElement element) + : m_chill(strToInt(element.attribute("chill"), 0)) + , m_direction(strToInt(element.attribute("direction"), 0)) + , m_speed(strToInt(element.attribute("speed"), 0)) +{} + +YahooWeatherResponse::Atmosphere::Atmosphere(QDomElement element) + : m_humidity(strToInt(element.attribute("humidity"), 0)) + , m_visibility(strToFloat(element.attribute("visibility"), 0.0) * 100) + , m_pressure(strToFloat(element.attribute("pressure"), 0.0)) + , m_pressureState(readPressure(element.attribute("rising"))) +{} + +YahooWeatherResponse::Astronomy::Astronomy(QDomElement element) + : m_sunrise(QTime::fromString(element.attribute("sunrise"), "h:mm ap")) + , m_sunset(QTime::fromString(element.attribute("sunset"), "h:mm ap")) +{} + +YahooWeatherResponse::Condition::Condition(QDomElement element) + : m_text(element.attribute("text")) + , m_code(strToInt(element.attribute("code"), 3200)) + , m_temperature(strToInt(element.attribute("temp"), 0)) + , m_date(readDateTime(element.attribute("date"))) +{} + +YahooWeatherResponse::Forecast::Forecast(QDomElement element) + : m_date(QDate::fromString(element.attribute("date"), "dd MMM yyyy")) + , m_low(strToInt(element.attribute("low"), 0)) + , m_high(strToInt(element.attribute("high"), 0)) + , m_text(element.attribute("text")) + , m_code(strToInt(element.attribute("code"), 3200)) +{} + +void YahooWeatherResponse::readItem(QDomElement item) +{ + int idx = 0; + for (int i = 0; i < item.childNodes().count(); ++i) { + QDomNode node = item.childNodes().at(i); + if (node.nodeName() == "geo:lat") + m_latitude = strToFloat(getNodeContentText(node), 0.0); + if (node.nodeName() == "geo:long") + m_longitude = strToFloat(getNodeContentText(node), 0.0); + if (node.nodeName() == "pubDate") + m_pubDate = readDateTime(getNodeContentText(node)); + if (node.nodeName() == "yweather:condition") + m_condition = Condition(node.toElement()); + if (node.nodeName() == "yweather:forecast") + m_forecast[idx++] = Forecast(node.toElement()); + idx = idx > 1 ? 0 : idx; + } +} + +YahooWeatherResponse::YahooWeatherResponse(const QString &locationCode, QDomElement element) + : m_locationCode(locationCode) +{ + QDomNodeList channels = element.elementsByTagName("channel"); + if (channels.count() == 0) + return; + QDomElement channel = channels.item(0).toElement(); + for (int i = 0; i < channel.childNodes().count(); ++i) { + QDomNode node = channel.childNodes().at(i); + + if (node.nodeName() == "lastBuildDate") + m_buildDate = readDateTime(getNodeContentText(node)); + + if (node.nodeName() == "ttl") + m_timeToLive = strToInt(getNodeContentText(node), 30); + + if (node.nodeName() == "yweather:location") + m_location = Location(node.toElement()); + + if (node.nodeName() == "yweather:units") + m_units = Units(node.toElement()); + + if (node.nodeName() == "yweather:wind") + m_wind = Wind(node.toElement()); + + if (node.nodeName() == "yweather:atmosphere") + m_atmosphere = Atmosphere(node.toElement()); + + if (node.nodeName() == "yweather:astronomy") + m_astronomy = Astronomy(node.toElement()); + + if (node.nodeName() == "item") + readItem(node.toElement()); + + } +} + +void YahooWeatherResponse::print() +{ + /* + qDebug() << "location code" << m_locationCode; + qDebug() << "buildDate" << m_buildDate; + qDebug() << "timeToLive" << m_timeToLive; + + qDebug() << "latitude" << m_latitude; + qDebug() << "longitude" << m_longitude; + qDebug() << "pubDate" << m_pubDate; + + qDebug() << "astronomy" << m_astronomy.sunrise() << m_astronomy.sunset(); + qDebug() << "atmosphere" << m_atmosphere.humidity() << m_atmosphere.visibility() << m_atmosphere.pressure() << m_atmosphere.pressureState(); + qDebug() << "condition" << m_condition.text() << m_condition.code() << m_condition.temperature() << m_condition.date(); + qDebug() << "forecast" << m_forecast[0].text() << m_forecast[0].code() << m_forecast[0].low() << m_forecast[0].high() << m_forecast[0].date(); + qDebug() << "forecast" << m_forecast[1].text() << m_forecast[1].code() << m_forecast[1].low() << m_forecast[1].high() << m_forecast[1].date(); + qDebug() << "location" << m_location.city() << m_location.region() << m_location.country(); + qDebug() << "units" << m_units.distance() << m_units.pressure() << m_units.speed() << m_units.temperature(); + qDebug() << "wind" << m_wind.chill() << m_wind.direction() << m_wind.speed(); + */ +} diff --git a/weather/src/yahooweatherresponse.h b/weather/src/yahooweatherresponse.h new file mode 100644 index 0000000..ac9d20e --- /dev/null +++ b/weather/src/yahooweatherresponse.h @@ -0,0 +1,181 @@ +#ifndef YAHOOWEATHERRESPONSE_H +#define YAHOOWEATHERRESPONSE_H + +#include <QString> +#include <QDomElement> +#include <QTime> +#include <QDate> +#include <QDateTime> + +class YahooWeatherResponse +{ +public: + enum TemperatureUnit { Fahrenheit, Celsius }; + enum DistanceUnit { Miles, Kilometers }; + enum PressureUnit { PoundsPerSquare, Millibars }; + enum SpeedUnit { MilesPerHour, KilometersPerHour }; + enum PressureState { Steady, Rising, Falling }; + + class Location + { + public: + Location() {}; + Location(QDomElement element); + QString city() const { return m_city; } + QString region() const { return m_region; } + QString country() const { return m_country; } + + private: + QString m_city; + QString m_region; + QString m_country; + }; + + class Units + { + public: + Units() {}; + Units(QDomElement element); + TemperatureUnit temperature() const { return m_temperature; } + DistanceUnit distance() const { return m_distance; } + PressureUnit pressure() const { return m_pressure; } + SpeedUnit speed() const { return m_speed; } + + private: + TemperatureUnit m_temperature; + DistanceUnit m_distance; + PressureUnit m_pressure; + SpeedUnit m_speed; + }; + + class Wind + { + public: + Wind() {}; + Wind(QDomElement element); + int chill() const { return m_chill; } + int direction() const { return m_direction; } + int speed() const { return m_speed; } + + private: + int m_chill; + int m_direction; + int m_speed; + }; + + class Atmosphere + { + public: + Atmosphere() {}; + Atmosphere(QDomElement element); + int humidity() const { return m_humidity; } + float visibility() const { return m_visibility; } + float pressure() const { return m_pressure; } + PressureState pressureState() const { return m_pressureState; } + + private: + int m_humidity; + float m_visibility; + float m_pressure; + PressureState m_pressureState; + }; + + class Astronomy + { + public: + Astronomy() {}; + Astronomy(QDomElement element); + QTime sunrise() const { return m_sunrise; } + QTime sunset() const { return m_sunset; } + + private: + QTime m_sunrise; + QTime m_sunset; + }; + + class Condition + { + public: + Condition() {}; + Condition(QDomElement element); + QString text() const { return m_text; } + int code() const { return m_code; } + int temperature() const { return m_temperature; } + QDateTime date() const { return m_date; } + + private: + QString m_text; + int m_code; + int m_temperature; + QDateTime m_date; + }; + + class Forecast + { + public: + Forecast() {}; + Forecast(QDomElement element); + + QDate date() const { return m_date; } + int low() const { return m_low; } + int high() const { return m_high; } + QString text() const { return m_text; } + int code() const { return m_code; } + + private: + QDate m_date; + int m_low; + int m_high; + QString m_text; + int m_code; + }; + +public: + YahooWeatherResponse(const QString &locationCode, QDomElement element); + + QString locationCode() const { return m_locationCode; } + + Location location() const { return m_location; } + Units units() const { return m_units; } + Wind wind() const { return m_wind; } + Atmosphere atmosphere() const { return m_atmosphere; } + Astronomy astronomy() const { return m_astronomy; } + Condition condition() const { return m_condition; } + Forecast forecast(int idx) const { return m_forecast[idx]; } + + QDateTime buildDate() const { return m_buildDate; } + int timeToLive() const { return m_timeToLive; } + float latitude() const { return m_latitude; } + float longitude() const { return m_longitude; } + QDateTime pubDate() const { return m_pubDate; } + + void print(); + + +private: + QString m_locationCode; + QDateTime m_buildDate; + int m_timeToLive; + float m_latitude; + float m_longitude; + QDateTime m_pubDate; + + Location m_location; + Units m_units; + Wind m_wind; + Atmosphere m_atmosphere; + Astronomy m_astronomy; + Condition m_condition; + Forecast m_forecast[2]; + + void readItem(QDomElement item); + +// <guid isPermaLink="false">USCA1116_2009_11_08_18_56_PST</guid> + + + + + +}; + +#endif // YAHOOWEATHERRESPONSE_H |