From 758cec1a767c056aaf9da36fd411cdf4a8fba32e Mon Sep 17 00:00:00 2001 From: "Javier S. Pedro" Date: Tue, 14 Aug 2012 00:11:51 +0200 Subject: rewrite live notifications handling using models --- libsowatch/declarativewatchlet.cpp | 6 +- libsowatch/declarativewatchwrapper.cpp | 13 -- libsowatch/declarativewatchwrapper.h | 3 - libsowatch/notificationsmodel.cpp | 94 +++++++++- libsowatch/notificationsmodel.h | 19 +- libsowatch/watchserver.cpp | 196 +++++++++------------ libsowatch/watchserver.h | 10 +- metawatch/metawatchdigitalsimulator.cpp | 1 + .../javispedro/sowatch/metawatch/MWListView.qml | 11 ++ notificationswatchlet/metawatch-digital.qml | 24 ++- testnotification/testnotification.cpp | 5 +- testnotification/testnotificationprovider.cpp | 4 + 12 files changed, 244 insertions(+), 142 deletions(-) diff --git a/libsowatch/declarativewatchlet.cpp b/libsowatch/declarativewatchlet.cpp index 510a68f..1851e38 100644 --- a/libsowatch/declarativewatchlet.cpp +++ b/libsowatch/declarativewatchlet.cpp @@ -23,7 +23,9 @@ DeclarativeWatchlet::DeclarativeWatchlet(WatchServer* server, const QString& id) if (!_registered) { qmlRegisterUncreatableType("com.javispedro.sowatch", 1, 0, - "Watch", "Watch is only available via the 'watch' object"); + "Watch", "Watch is only available via the 'watch' context property"); + qmlRegisterUncreatableType("com.javispedro.sowatch", 1, 0, + "NotificationsModel", "NotificationsModel is only available via the 'notifications' context property"); qmlRegisterType(); qmlRegisterType("com.javispedro.sowatch", 1, 0, "GConfKey"); _registered = true; @@ -40,6 +42,8 @@ DeclarativeWatchlet::DeclarativeWatchlet(WatchServer* server, const QString& id) _wrapper = new DeclarativeWatchWrapper(server, server->watch(), this); _engine->rootContext()->setContextProperty("watch", _wrapper); + _engine->rootContext()->setContextProperty("notifications", + const_cast(server->notifications())); } DeclarativeWatchlet::~DeclarativeWatchlet() diff --git a/libsowatch/declarativewatchwrapper.cpp b/libsowatch/declarativewatchwrapper.cpp index ebf92b5..968c3e2 100644 --- a/libsowatch/declarativewatchwrapper.cpp +++ b/libsowatch/declarativewatchwrapper.cpp @@ -23,19 +23,6 @@ bool DeclarativeWatchWrapper::active() const return _active; } -QList DeclarativeWatchWrapper::notifications() const -{ - // TODO: Figure out a better way for this; QAbstractListModel, etc. - QList nl = _server->liveNotifications(); - QList ol; - foreach (Notification* n, nl) { - QObject * o = n; - ol.append(o); - } - qDebug() << "notifications to declarative: " << ol; - return ol; -} - void DeclarativeWatchWrapper::vibrate(int msecs) { if (_active) { diff --git a/libsowatch/declarativewatchwrapper.h b/libsowatch/declarativewatchwrapper.h index 8d4fd7d..74f569c 100644 --- a/libsowatch/declarativewatchwrapper.h +++ b/libsowatch/declarativewatchwrapper.h @@ -17,7 +17,6 @@ class SOWATCH_EXPORT DeclarativeWatchWrapper : public QObject Q_OBJECT Q_PROPERTY(QString model READ model CONSTANT) Q_PROPERTY(bool active READ active NOTIFY activeChanged) - Q_PROPERTY(QList notifications READ notifications NOTIFY notificationsChanged) public: explicit DeclarativeWatchWrapper(WatchServer *server, Watch *watch, QObject *parent = 0); @@ -25,8 +24,6 @@ public: QString model() const; bool active() const; - QList notifications() const; - public slots: void vibrate(int msecs); diff --git a/libsowatch/notificationsmodel.cpp b/libsowatch/notificationsmodel.cpp index 2eba9a0..73d4ab6 100644 --- a/libsowatch/notificationsmodel.cpp +++ b/libsowatch/notificationsmodel.cpp @@ -12,6 +12,12 @@ using namespace sowatch; NotificationsModel::NotificationsModel(QObject *parent) : QAbstractListModel(parent) { + QHash roles = roleNames(); + roles[Qt::DisplayRole] = QByteArray("title"); + roles[ObjectRole] = QByteArray("object"); + roles[BodyRole] = QByteArray("body"); + roles[CountRole] = QByteArray("count"); + setRoleNames(roles); } int NotificationsModel::rowCount(const QModelIndex &parent) const @@ -26,15 +32,51 @@ int NotificationsModel::rowCount(const QModelIndex &parent) const QVariant NotificationsModel::data(const QModelIndex &index, int role) const { + const Notification *n = getNotificationByIndex(index.row()); + if (!n) return QVariant(); + switch (role) { + case Qt::DisplayRole: + return QVariant::fromValue(n->title()); + case ObjectRole: + return QVariant::fromValue(const_cast(n)); + case BodyRole: + return QVariant::fromValue(n->body()); + case CountRole: + return QVariant::fromValue(n->count()); + } + return QVariant(); } void NotificationsModel::add(Notification *n) { + const Notification::Type type = n->type(); + const int offset = getAppendOffsetForType(type); + + beginInsertRows(QModelIndex(), offset, offset); + _list[type].append(n); + endInsertRows(); + + connect(n, SIGNAL(changed()), SLOT(handleNotificationChanged())); } void NotificationsModel::remove(Notification *n) { + const Notification::Type type = n->type(); + remove(type, n); +} + +void NotificationsModel::remove(Notification::Type type, Notification *n) +{ + const int subindex = _list[type].indexOf(n); + const int index = getOffsetForType(type) + subindex; + + Q_ASSERT(index >= 0); + + disconnect(n, 0, this, 0); + beginRemoveRows(QModelIndex(), index, index); + _list[type].removeAt(subindex); + endRemoveRows(); } int NotificationsModel::fullCount() const @@ -55,16 +97,62 @@ int NotificationsModel::fullCountByType(Notification::Type type) const return count; } -bool NotificationsModel::removeDeletedNotification(Notification *n) +Notification::Type NotificationsModel::getTypeOfDeletedNotification(Notification *n) const { // Can't call any methods of 'n' + FOREACH_TYPE(type) { + if (_list[type].contains(n)) { + return type; + } + } + return Notification::OtherNotification; } -int NotificationsModel::getOffsetForType(Notification::Type type) +int NotificationsModel::getOffsetForType(Notification::Type type) const { int count = 0; FOREACH_TYPE_UNTIL(t, type) { - count += _list[type].count(); + count += _list[t].count(); } return count; } + +int NotificationsModel::getAppendOffsetForType(Notification::Type type) const +{ + return getOffsetForType(type) + _list[type].count(); +} + +int NotificationsModel::getIndexForNotification(Notification *n) const +{ + Notification::Type type = n->type(); + const int subindex = _list[type].indexOf(n); + + Q_ASSERT(subindex >= 0); + + return getOffsetForType(type) + subindex; +} + +const Notification * NotificationsModel::getNotificationByIndex(int index) const +{ + FOREACH_TYPE(type) { + const int size = _list[type].size(); + if (index < size) { + return _list[type].at(index); + } else { + index -= size; + } + } + qWarning() << "Notification with index" << index << "not found"; + return 0; +} + +void NotificationsModel::handleNotificationChanged() +{ + QObject *obj = sender(); + if (obj) { + Notification* n = static_cast(obj); + const int index = getIndexForNotification(n); + + emit dataChanged(createIndex(index, 0), createIndex(index, 0)); + } +} diff --git a/libsowatch/notificationsmodel.h b/libsowatch/notificationsmodel.h index 5e7e029..6aabf71 100644 --- a/libsowatch/notificationsmodel.h +++ b/libsowatch/notificationsmodel.h @@ -14,19 +14,32 @@ class NotificationsModel : public QAbstractListModel public: explicit NotificationsModel(QObject *parent = 0); + enum DataRoles { + ObjectRole = Qt::UserRole, + BodyRole, + CountRole + }; + int rowCount(const QModelIndex &parent) const; QVariant data(const QModelIndex &index, int role) const; void add(Notification *n); void remove(Notification *n); + void remove(Notification::Type type, Notification *n); int fullCount() const; int fullCountByType(Notification::Type type) const; - bool removeDeletedNotification(Notification *n); + Notification::Type getTypeOfDeletedNotification(Notification *n) const; private: - int getOffsetForType(Notification::Type type); + int getOffsetForType(Notification::Type type) const; + int getAppendOffsetForType(Notification::Type type) const; + int getIndexForNotification(Notification *n) const; + const Notification* getNotificationByIndex(int index) const; + +private slots: + void handleNotificationChanged(); private: QList _list[Notification::TypeCount]; @@ -34,4 +47,6 @@ private: } +QML_DECLARE_TYPE(sowatch::NotificationsModel) + #endif // SOWATCH_NOTIFICATIONSMODEL_H diff --git a/libsowatch/watchserver.cpp b/libsowatch/watchserver.cpp index a6b5886..01af063 100644 --- a/libsowatch/watchserver.cpp +++ b/libsowatch/watchserver.cpp @@ -11,6 +11,7 @@ WatchServer::WatchServer(Watch* watch, QObject* parent) : QObject(parent), _watch(watch), _nextWatchletButton(-1), _oldNotificationThreshold(300), + _notifications(new NotificationsModel(this)), _currentWatchlet(0), _currentWatchletActive(false), _currentWatchletIndex(-1), _syncTimeTimer(new QTimer(this)) { @@ -97,15 +98,66 @@ void WatchServer::removeProvider(const NotificationProvider *provider) this, SLOT(postNotification(Notification*))); } -QList WatchServer::liveNotifications() +const NotificationsModel * WatchServer::notifications() const { - QList notifications; + return _notifications; +} + +void WatchServer::postNotification(Notification *notification) +{ + const Notification::Type type = notification->type(); + + // Add notification to model + _notifications->add(notification); + _notificationCounts[notification] = notification->count(); + + connect(notification, SIGNAL(changed()), SLOT(handleNotificationChanged())); + connect(notification, SIGNAL(dismissed()), SLOT(handleNotificationDismissed())); + connect(notification, SIGNAL(destroyed()), SLOT(handleNotificationDestroyed())); - for (int i = 0; i < Notification::TypeCount; i++) { - notifications.append(_notifications[i]); + qDebug() << "notification received" << notification->title() << "(" << notification->count() << ")"; + + _watch->updateNotificationCount(type, getNotificationCount(type)); + + if (type == Notification::WeatherNotification) { + // Weather notifications, we handle differently. + WeatherNotification* weather = static_cast(notification); + _weather = weather; + _watch->updateWeather(weather); + return; // And do not display it the usual way } - return notifications; + QDateTime oldThreshold = QDateTime::currentDateTime().addSecs(-_oldNotificationThreshold); + if (notification->dateTime() < oldThreshold) { + return; // Do not care about notifications that old... + } + + if (_pendingNotifications.isEmpty()) { + _pendingNotifications.enqueue(notification); + nextNotification(); + } else if (type == Notification::CallNotification) { + // Oops, priority!!!! + _pendingNotifications.prepend(notification); + nextNotification(); + } else { + _pendingNotifications.enqueue(notification); + } +} + +void WatchServer::nextNotification() +{ + if (!_watch->isConnected()) return; + if (!_pendingNotifications.empty()) { + Notification *n = _pendingNotifications.head(); + if (_currentWatchlet && _currentWatchletActive) { + deactivateCurrentWatchlet(); + } + _watch->displayNotification(n); + } else if (_currentWatchlet) { + reactivateCurrentWatchlet(); + } else { + goToIdle(); + } } void WatchServer::runWatchlet(Watchlet *watchlet) @@ -182,11 +234,33 @@ void WatchServer::syncTime() uint WatchServer::getNotificationCount(Notification::Type type) { - uint count = 0; - foreach (Notification* n, _notifications[type]) { - count += n->count(); + return _notifications->fullCountByType(type); +} + +void WatchServer::removeNotification(Notification::Type type, Notification *n) +{ + // Warning: This function might be called with n being deleted. + _notifications->remove(type, n); + _notificationCounts.remove(n); + + _watch->updateNotificationCount(type, getNotificationCount(type)); + + if (!_pendingNotifications.isEmpty() && _pendingNotifications.head() == n) { + qDebug() << "removing top notification"; + _pendingNotifications.removeAll(n); + nextNotification(); + } else { + _pendingNotifications.removeAll(n); } - return count; + if (type == Notification::WeatherNotification) { + WeatherNotification* w = static_cast(n); + if (_weather == w) { + _weather = 0; + } + } + + // No longer interested in this notification + disconnect(n, 0, this, 0); } void WatchServer::goToIdle() @@ -242,61 +316,6 @@ void WatchServer::handleWatchButtonPress(int button) } } -void WatchServer::postNotification(Notification *notification) -{ - const Notification::Type type = notification->type(); - _notifications[type].append(notification); - _notificationCounts[notification] = notification->count(); - - connect(notification, SIGNAL(changed()), SLOT(handleNotificationChanged())); - connect(notification, SIGNAL(dismissed()), SLOT(handleNotificationDismissed())); - connect(notification, SIGNAL(destroyed()), SLOT(handleNotificationDestroyed())); - - qDebug() << "notification received" << notification->title() << "(" << notification->count() << ")"; - - _watch->updateNotificationCount(type, getNotificationCount(type)); - - if (type == Notification::WeatherNotification) { - // Weather notifications, we handle differently. - WeatherNotification* weather = static_cast(notification); - _weather = weather; - _watch->updateWeather(weather); - return; // And do not display it the usual way - } - - QDateTime oldThreshold = QDateTime::currentDateTime().addSecs(-_oldNotificationThreshold); - if (notification->dateTime() < oldThreshold) { - return; // Do not care about notifications that old... - } - - if (_pendingNotifications.isEmpty()) { - _pendingNotifications.enqueue(notification); - nextNotification(); - } else if (type == Notification::CallNotification) { - // Oops, priority!!!! - _pendingNotifications.prepend(notification); - nextNotification(); - } else { - _pendingNotifications.enqueue(notification); - } -} - -void WatchServer::nextNotification() -{ - if (!_watch->isConnected()) return; - if (!_pendingNotifications.empty()) { - Notification *n = _pendingNotifications.head(); - if (_currentWatchlet && _currentWatchletActive) { - deactivateCurrentWatchlet(); - } - _watch->displayNotification(n); - } else if (_currentWatchlet) { - reactivateCurrentWatchlet(); - } else { - goToIdle(); - } -} - void WatchServer::handleNotificationChanged() { QObject *obj = sender(); @@ -346,29 +365,8 @@ void WatchServer::handleNotificationDismissed() if (obj) { Notification* n = static_cast(obj); const Notification::Type type = n->type(); - _notifications[type].removeOne(n); - _notificationCounts.remove(n); - qDebug() << "notification dismissed" << n->title() << "(" << n->count() << ")"; - - _watch->updateNotificationCount(type, getNotificationCount(type)); - - if (!_pendingNotifications.isEmpty() && _pendingNotifications.head() == n) { - qDebug() << "removing top notification"; - _pendingNotifications.removeAll(n); - nextNotification(); - } else { - _pendingNotifications.removeAll(n); - } - if (type == Notification::WeatherNotification) { - WeatherNotification* w = static_cast(n); - if (_weather == w) { - _weather = 0; - } - } - - // No longer interested in this notification - disconnect(n, 0, this, 0); + removeNotification(type, n); } } @@ -380,30 +378,8 @@ void WatchServer::handleNotificationDestroyed() // Cannot call any methods of n; it is a dangling pointer now. if (_notificationCounts.contains(n)) { qWarning() << "Notification destroyed without being dismissed!"; - _notificationCounts.remove(n); - - for (int i = 0; i < Notification::TypeCount; i++) { - Notification::Type type = static_cast(i); - if (_notifications[type].contains(n)) { - _notifications[type].removeAll(n); - _watch->updateNotificationCount(type, getNotificationCount(type)); - - if (type == Notification::WeatherNotification) { - WeatherNotification* w = static_cast(n); - if (_weather == w) { - _weather = 0; - } - } - } - } - - if (!_pendingNotifications.isEmpty() && _pendingNotifications.head() == n) { - qDebug() << "removing top notification"; - _pendingNotifications.removeAll(n); - nextNotification(); - } else { - _pendingNotifications.removeAll(n); - } + Notification::Type type = _notifications->getTypeOfDeletedNotification(n); + removeNotification(type, n); } } } diff --git a/libsowatch/watchserver.h b/libsowatch/watchserver.h index af2a8de..91f9b4e 100644 --- a/libsowatch/watchserver.h +++ b/libsowatch/watchserver.h @@ -8,7 +8,7 @@ #include #include "sowatch_global.h" -#include "notification.h" +#include "notificationsmodel.h" namespace sowatch { @@ -41,7 +41,7 @@ public: void removeProvider(const NotificationProvider *provider); /** Get a list of all current live notifications. */ - QList liveNotifications(); + const NotificationsModel * notifications() const; public slots: void postNotification(Notification *notification); @@ -71,8 +71,8 @@ private: /** Stores all the watchlets with a given watchled id. */ QMap _watchletIds; - /** Stores current live notifications, classified by type. */ - QList _notifications[Notification::TypeCount]; + /** Stores current live notifications. */ + NotificationsModel *_notifications; /** A list of notifications that are yet to be shown to the user. */ QQueue _pendingNotifications; /** Stores the count of notifications hidden between each notification object. */ @@ -92,6 +92,8 @@ private: /** Counts all notifications from a given type. */ uint getNotificationCount(Notification::Type type); + /** Remove a notification of a certain type. */ + void removeNotification(Notification::Type type, Notification* n); void deactivateCurrentWatchlet(); void reactivateCurrentWatchlet(); diff --git a/metawatch/metawatchdigitalsimulator.cpp b/metawatch/metawatchdigitalsimulator.cpp index f12e987..5c399bd 100644 --- a/metawatch/metawatchdigitalsimulator.cpp +++ b/metawatch/metawatchdigitalsimulator.cpp @@ -116,4 +116,5 @@ void MetaWatchDigitalSimulator::retryConnect() void MetaWatchDigitalSimulator::send(const Message &msg) { // Do not send messages + Q_UNUSED(msg); } diff --git a/metawatch/qml/com/javispedro/sowatch/metawatch/MWListView.qml b/metawatch/qml/com/javispedro/sowatch/metawatch/MWListView.qml index 6af7b18..e16869f 100644 --- a/metawatch/qml/com/javispedro/sowatch/metawatch/MWListView.qml +++ b/metawatch/qml/com/javispedro/sowatch/metawatch/MWListView.qml @@ -67,6 +67,17 @@ ListView { } } + function scrollTop() { + if (count == 0) { + return; + } + if (selectable) { + currentIndex = 0; + } + positionViewAtIndex(0, ListView.Beginning); + + } + Rectangle { id: indicatorCont visible: list.indicator && (list.contentHeight > list.height) diff --git a/notificationswatchlet/metawatch-digital.qml b/notificationswatchlet/metawatch-digital.qml index 57a6176..fcd1081 100644 --- a/notificationswatchlet/metawatch-digital.qml +++ b/notificationswatchlet/metawatch-digital.qml @@ -18,7 +18,7 @@ MWPage { anchors.right: parent.right anchors.bottom: parent.bottom clip: true - model: watch.notifications + model: notifications delegate: Rectangle { id: notifDelegate @@ -30,14 +30,14 @@ MWPage { width: parent.width MWLabel { width: parent.width - text: model.modelData.title + text: title wrapMode: Text.WrapAtWordBoundaryOrAnywhere color: notifDelegate.selected ? "white" : "black" font.pointSize: 12 } MWLabel { width: parent.width - text: model.modelData.body + text: body wrapMode: Text.WrapAtWordBoundaryOrAnywhere color: notifDelegate.selected ? "white" : "black" } @@ -56,7 +56,7 @@ MWPage { Connections { target: watch - onButtonPressed : { + onButtonPressed: { switch (button) { case 1: notifs.scrollUp(); @@ -67,4 +67,20 @@ MWPage { } } } + + Connections { + target: notifications + onRowsInserted: { + if (!watch.active) { + // Always position into the topmost notification if + // user is not looking at this list + notifs.scrollTop(); + } + } + onRowsRemoved: { + if (!watch.active) { + notifs.scrollTop(); + } + } + } } diff --git a/testnotification/testnotification.cpp b/testnotification/testnotification.cpp index fa904b9..d541287 100644 --- a/testnotification/testnotification.cpp +++ b/testnotification/testnotification.cpp @@ -8,6 +8,9 @@ TestNotification::TestNotification(Type type, const QString &title, const QStrin _time(QDateTime::currentDateTime()), _title(title), _body(body) { + const int high = 30000, low = 10000; + int rand = qrand() % ((high + 1) - low) + low; + QTimer::singleShot(rand, this, SIGNAL(dismissed())); } Notification::Type TestNotification::type() const @@ -44,5 +47,3 @@ void TestNotification::dismiss() { deleteLater(); // We do not want to keep those around. } - - diff --git a/testnotification/testnotificationprovider.cpp b/testnotification/testnotificationprovider.cpp index f66ae89..230237f 100644 --- a/testnotification/testnotificationprovider.cpp +++ b/testnotification/testnotificationprovider.cpp @@ -13,6 +13,10 @@ TestNotificationProvider::TestNotificationProvider(QObject *parent) : QTimer::singleShot(1200, this, SLOT(generateNotification())); QTimer::singleShot(1400, this, SLOT(generateNotification())); QTimer::singleShot(1600, this, SLOT(generateNotification())); + QTimer::singleShot(1800, this, SLOT(generateNotification())); + QTimer::singleShot(2000, this, SLOT(generateNotification())); + QTimer::singleShot(2200, this, SLOT(generateNotification())); + QTimer::singleShot(2400, this, SLOT(generateInitialNotification())); connect(_timer, SIGNAL(timeout()), SLOT(generateNotification())); _timer->setInterval(60000); //_timer->start(); -- cgit v1.2.3