From 2c2a18af43360f7846d2cadec46774e356bfcf17 Mon Sep 17 00:00:00 2001 From: Klaas Freitag Date: Fri, 11 Mar 2016 11:37:45 +0100 Subject: [PATCH] Activitiy: Refactor - move classes to their own source files. Created a activitydata.h header (only) for the basic data, plus a separate file for the model. Cleans up the widget source. --- src/gui/CMakeLists.txt | 1 + src/gui/activitydata.h | 106 +++++++++++++++ src/gui/activitylistmodel.cpp | 237 ++++++++++++++++++++++++++++++++++ src/gui/activitylistmodel.h | 67 ++++++++++ src/gui/activitywidget.cpp | 232 +-------------------------------- src/gui/activitywidget.h | 110 +--------------- src/gui/notificationwidget.h | 4 +- 7 files changed, 417 insertions(+), 340 deletions(-) create mode 100644 src/gui/activitydata.h create mode 100644 src/gui/activitylistmodel.cpp create mode 100644 src/gui/activitylistmodel.h diff --git a/src/gui/CMakeLists.txt b/src/gui/CMakeLists.txt index 031a1f6f5..92b9b5008 100644 --- a/src/gui/CMakeLists.txt +++ b/src/gui/CMakeLists.txt @@ -63,6 +63,7 @@ set(client_SRCS owncloudgui.cpp owncloudsetupwizard.cpp protocolwidget.cpp + activitylistmodel.cpp activitywidget.cpp activityitemdelegate.cpp selectivesyncdialog.cpp diff --git a/src/gui/activitydata.h b/src/gui/activitydata.h new file mode 100644 index 000000000..46efe2c1f --- /dev/null +++ b/src/gui/activitydata.h @@ -0,0 +1,106 @@ +/* + * Copyright (C) by Klaas Freitag + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; version 2 of the License. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY + * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * for more details. + */ + +#ifndef ACTIVITYDATA_H +#define ACTIVITYDATA_H + +#include + +namespace OCC { +/** + * @brief The ActivityLink class describes actions of an activity + * + * These are part of notifications which are mapped into activities. + */ + +class ActivityLink +{ +public: + QHash toVariantHash() { + QHash hash; + + hash["label"] = _label; + hash["link"] = _link; + hash["verb"] = _verb; + hash["primary"] = _isPrimary; + + return hash; + } + + QString _label; + QString _link; + QString _verb; + bool _isPrimary; +}; + +/* ==================================================================== */ +/** + * @brief Activity Structure + * @ingroup gui + * + * contains all the information describing a single activity. + */ + +class Activity +{ +public: + enum Type { + ActivityType, + NotificationType + }; + Type _type; + qlonglong _id; + QString _subject; + QString _message; + QString _file; + QUrl _link; + QDateTime _dateTime; + QString _accName; + + QVector _links; + /** + * @brief Sort operator to sort the list youngest first. + * @param val + * @return + */ + bool operator<( const Activity& val ) const { + return _dateTime.toMSecsSinceEpoch() > val._dateTime.toMSecsSinceEpoch(); + } + +}; + +/* ==================================================================== */ +/** + * @brief The ActivityList + * @ingroup gui + * + * A QList based list of Activities + */ +class ActivityList:public QList +{ +public: + void setAccountName( const QString& name ) { + _accountName = name; + } + + QString accountName() const { + return _accountName; + } + +private: + QString _accountName; +}; + +} + +#endif // ACTIVITYDATA_H diff --git a/src/gui/activitylistmodel.cpp b/src/gui/activitylistmodel.cpp new file mode 100644 index 000000000..8fa3ff462 --- /dev/null +++ b/src/gui/activitylistmodel.cpp @@ -0,0 +1,237 @@ +/* + * Copyright (C) by Klaas Freitag + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; version 2 of the License. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY + * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * for more details. + */ + +#include +#include +#include +#include + +#include "account.h" +#include "accountstate.h" +#include "accountmanager.h" +#include "folderman.h" +#include "accessmanager.h" +#include "activityitemdelegate.h" + +#include "activitydata.h" +#include "activitylistmodel.h" + +namespace OCC { + +ActivityListModel::ActivityListModel(QWidget *parent) + :QAbstractListModel(parent) +{ +} + +QVariant ActivityListModel::data(const QModelIndex &index, int role) const +{ + Activity a; + + if (!index.isValid()) + return QVariant(); + + a = _finalList.at(index.row()); + AccountStatePtr ast = AccountManager::instance()->account(a._accName); + QStringList list; + + if (role == Qt::EditRole) + return QVariant(); + + switch (role) { + case ActivityItemDelegate::PathRole: + list = FolderMan::instance()->findFileInLocalFolders(a._file, ast->account()); + if( list.count() > 0 ) { + return QVariant(list.at(0)); + } + // File does not exist anymore? Let's try to open its path + list = FolderMan::instance()->findFileInLocalFolders(QFileInfo(a._file).path(), ast->account()); + if( list.count() > 0 ) { + return QVariant(list.at(0)); + } + return QVariant(); + break; + case ActivityItemDelegate::ActionIconRole: + return QVariant(); // FIXME once the action can be quantified, display on Icon + break; + case ActivityItemDelegate::UserIconRole: + return QIcon(QLatin1String(":/client/resources/account.png")); + break; + case Qt::ToolTipRole: + case ActivityItemDelegate::ActionTextRole: + return a._subject; + break; + case ActivityItemDelegate::LinkRole: + return a._link; + break; + case ActivityItemDelegate::AccountRole: + return a._accName; + break; + case ActivityItemDelegate::PointInTimeRole: + return Utility::timeAgoInWords(a._dateTime); + break; + case ActivityItemDelegate::AccountConnectedRole: + return (ast && ast->isConnected()); + break; + default: + return QVariant(); + + } + return QVariant(); + +} + +int ActivityListModel::rowCount(const QModelIndex&) const +{ + return _finalList.count(); +} + +// current strategy: Fetch 100 items per Account +// ATTENTION: This method is const and thus it is not possible to modify +// the _activityLists hash or so. Doesn't make it easier... +bool ActivityListModel::canFetchMore(const QModelIndex& ) const +{ + if( _activityLists.count() == 0 ) return true; + + QMap::const_iterator i = _activityLists.begin(); + while (i != _activityLists.end()) { + AccountState *ast = i.key(); + if( ast && ast->isConnected() ) { + ActivityList activities = i.value(); + if( activities.count() == 0 && + ! _currentlyFetching.contains(ast) ) { + return true; + } + } + ++i; + } + + return false; +} + +void ActivityListModel::startFetchJob(AccountState* s) +{ + if( !s->isConnected() ) { + return; + } + JsonApiJob *job = new JsonApiJob(s->account(), QLatin1String("ocs/v1.php/cloud/activity"), this); + QObject::connect(job, SIGNAL(jsonReceived(QVariantMap, int)), + this, SLOT(slotActivitiesReceived(QVariantMap, int))); + job->setProperty("AccountStatePtr", QVariant::fromValue(s)); + + QList< QPair > params; + params.append(qMakePair(QString::fromLatin1("page"), QString::fromLatin1("0"))); + params.append(qMakePair(QString::fromLatin1("pagesize"), QString::fromLatin1("100"))); + job->addQueryParams(params); + + _currentlyFetching.insert(s); + qDebug() << "Start fetching activities for " << s->account()->displayName(); + job->start(); +} + +void ActivityListModel::slotActivitiesReceived(const QVariantMap& json, int statusCode) +{ + auto activities = json.value("ocs").toMap().value("data").toList(); + // qDebug() << "*** activities" << activities; + + ActivityList list; + AccountState* ast = qvariant_cast(sender()->property("AccountStatePtr")); + _currentlyFetching.remove(ast); + list.setAccountName( ast->account()->displayName()); + + foreach( auto activ, activities ) { + auto json = activ.toMap(); + + Activity a; + a._type = Activity::ActivityType; + a._accName = ast->account()->displayName(); + a._id = json.value("id").toLongLong(); + a._subject = json.value("subject").toString(); + a._message = json.value("message").toString(); + a._file = json.value("file").toString(); + a._link = json.value("link").toUrl(); + a._dateTime = json.value("date").toDateTime(); + a._dateTime.setTimeSpec(Qt::UTC); + list.append(a); + } + + _activityLists[ast] = list; + + emit activityJobStatusCode(ast, statusCode); + + combineActivityLists(); +} + + +void ActivityListModel::combineActivityLists() +{ + ActivityList resultList; + + foreach( ActivityList list, _activityLists.values() ) { + resultList.append(list); + } + + std::sort( resultList.begin(), resultList.end() ); + + beginInsertRows(QModelIndex(), 0, resultList.count()-1); + _finalList = resultList; + endInsertRows(); +} + +void ActivityListModel::fetchMore(const QModelIndex &) +{ + QList accounts = AccountManager::instance()->accounts(); + + foreach (AccountStatePtr asp, accounts) { + bool newItem = false; + + if( !_activityLists.contains(asp.data()) && asp->isConnected() ) { + _activityLists[asp.data()] = ActivityList(); + newItem = true; + } + if( newItem ) { + startFetchJob(asp.data()); + } + } +} + +void ActivityListModel::slotRefreshActivity(AccountState *ast) +{ + if(ast && _activityLists.contains(ast)) { + qDebug() << "**** Refreshing Activity list for" << ast->account()->displayName(); + _activityLists.remove(ast); + } + startFetchJob(ast); +} + +void ActivityListModel::slotRemoveAccount(AccountState *ast ) +{ + if( _activityLists.contains(ast) ) { + int i = 0; + const QString accountToRemove = ast->account()->displayName(); + + QMutableListIterator it(_finalList); + + while (it.hasNext()) { + Activity activity = it.next(); + if( activity._accName == accountToRemove ) { + beginRemoveRows(QModelIndex(), i, i+1); + it.remove(); + endRemoveRows(); + } + } + _activityLists.remove(ast); + _currentlyFetching.remove(ast); + } +} + +} diff --git a/src/gui/activitylistmodel.h b/src/gui/activitylistmodel.h new file mode 100644 index 000000000..89e5e46ad --- /dev/null +++ b/src/gui/activitylistmodel.h @@ -0,0 +1,67 @@ +/* + * Copyright (C) by Klaas Freitag + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; version 2 of the License. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY + * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * for more details. + */ + +#ifndef ACTIVITYLISTMODEL_H +#define ACTIVITYLISTMODEL_H + +#include + +#include "activitydata.h" + +namespace OCC { + +class AccountState; + +/** + * @brief The ActivityListModel + * @ingroup gui + * + * Simple list model to provide the list view with data. + */ + +class ActivityListModel : public QAbstractListModel +{ + Q_OBJECT +public: + explicit ActivityListModel(QWidget *parent=0); + + QVariant data(const QModelIndex &index, int role) const Q_DECL_OVERRIDE; + int rowCount(const QModelIndex& parent = QModelIndex()) const Q_DECL_OVERRIDE; + + bool canFetchMore(const QModelIndex& ) const Q_DECL_OVERRIDE; + void fetchMore(const QModelIndex&) Q_DECL_OVERRIDE; + + ActivityList activityList() { return _finalList; } + +public slots: + void slotRefreshActivity(AccountState* ast); + void slotRemoveAccount( AccountState *ast ); + +private slots: + void slotActivitiesReceived(const QVariantMap& json, int statusCode); + +signals: + void activityJobStatusCode(AccountState* ast, int statusCode); + +private: + void startFetchJob(AccountState* s); + void combineActivityLists(); + + QMap _activityLists; + ActivityList _finalList; + QSet _currentlyFetching; +}; + + +} +#endif // ACTIVITYLISTMODEL_H diff --git a/src/gui/activitywidget.cpp b/src/gui/activitywidget.cpp index 01f5b4865..611020775 100644 --- a/src/gui/activitywidget.cpp +++ b/src/gui/activitywidget.cpp @@ -16,6 +16,7 @@ #include #endif +#include "activitylistmodel.h" #include "activitywidget.h" #include "configfile.h" #include "syncresult.h" @@ -43,237 +44,6 @@ namespace OCC { -void ActivityList::setAccountName( const QString& name ) -{ - _accountName = name; -} - -QString ActivityList::accountName() const -{ - return _accountName; -} - -/* ==================================================================== */ - -QHash ActivityLink::toVariantHash() -{ - QHash hash; - - hash["label"] = _label; - hash["link"] = _link; - hash["verb"] = _verb; - hash["primary"] = _isPrimary; - - return hash; -} - -/* ==================================================================== */ - -ActivityListModel::ActivityListModel(QWidget *parent) - :QAbstractListModel(parent) -{ -} - -QVariant ActivityListModel::data(const QModelIndex &index, int role) const -{ - Activity a; - - if (!index.isValid()) - return QVariant(); - - a = _finalList.at(index.row()); - AccountStatePtr ast = AccountManager::instance()->account(a._accName); - QStringList list; - - if (role == Qt::EditRole) - return QVariant(); - - switch (role) { - case ActivityItemDelegate::PathRole: - list = FolderMan::instance()->findFileInLocalFolders(a._file, ast->account()); - if( list.count() > 0 ) { - return QVariant(list.at(0)); - } - // File does not exist anymore? Let's try to open its path - list = FolderMan::instance()->findFileInLocalFolders(QFileInfo(a._file).path(), ast->account()); - if( list.count() > 0 ) { - return QVariant(list.at(0)); - } - return QVariant(); - break; - case ActivityItemDelegate::ActionIconRole: - return QVariant(); // FIXME once the action can be quantified, display on Icon - break; - case ActivityItemDelegate::UserIconRole: - return QIcon(QLatin1String(":/client/resources/account.png")); - break; - case Qt::ToolTipRole: - case ActivityItemDelegate::ActionTextRole: - return a._subject; - break; - case ActivityItemDelegate::LinkRole: - return a._link; - break; - case ActivityItemDelegate::AccountRole: - return a._accName; - break; - case ActivityItemDelegate::PointInTimeRole: - return Utility::timeAgoInWords(a._dateTime); - break; - case ActivityItemDelegate::AccountConnectedRole: - return (ast && ast->isConnected()); - break; - default: - return QVariant(); - - } - return QVariant(); - -} - -int ActivityListModel::rowCount(const QModelIndex&) const -{ - return _finalList.count(); -} - -// current strategy: Fetch 100 items per Account -// ATTENTION: This method is const and thus it is not possible to modify -// the _activityLists hash or so. Doesn't make it easier... -bool ActivityListModel::canFetchMore(const QModelIndex& ) const -{ - if( _activityLists.count() == 0 ) return true; - - QMap::const_iterator i = _activityLists.begin(); - while (i != _activityLists.end()) { - AccountState *ast = i.key(); - if( ast && ast->isConnected() ) { - ActivityList activities = i.value(); - if( activities.count() == 0 && - ! _currentlyFetching.contains(ast) ) { - return true; - } - } - ++i; - } - - return false; -} - -void ActivityListModel::startFetchJob(AccountState* s) -{ - if( !s->isConnected() ) { - return; - } - JsonApiJob *job = new JsonApiJob(s->account(), QLatin1String("ocs/v1.php/cloud/activity"), this); - QObject::connect(job, SIGNAL(jsonReceived(QVariantMap, int)), - this, SLOT(slotActivitiesReceived(QVariantMap, int))); - job->setProperty("AccountStatePtr", QVariant::fromValue(s)); - - QList< QPair > params; - params.append(qMakePair(QString::fromLatin1("page"), QString::fromLatin1("0"))); - params.append(qMakePair(QString::fromLatin1("pagesize"), QString::fromLatin1("100"))); - job->addQueryParams(params); - - _currentlyFetching.insert(s); - qDebug() << "Start fetching activities for " << s->account()->displayName(); - job->start(); -} - -void ActivityListModel::slotActivitiesReceived(const QVariantMap& json, int statusCode) -{ - auto activities = json.value("ocs").toMap().value("data").toList(); - // qDebug() << "*** activities" << activities; - - ActivityList list; - AccountState* ast = qvariant_cast(sender()->property("AccountStatePtr")); - _currentlyFetching.remove(ast); - list.setAccountName( ast->account()->displayName()); - - foreach( auto activ, activities ) { - auto json = activ.toMap(); - - Activity a; - a._type = Activity::ActivityType; - a._accName = ast->account()->displayName(); - a._id = json.value("id").toLongLong(); - a._subject = json.value("subject").toString(); - a._message = json.value("message").toString(); - a._file = json.value("file").toString(); - a._link = json.value("link").toUrl(); - a._dateTime = json.value("date").toDateTime(); - a._dateTime.setTimeSpec(Qt::UTC); - list.append(a); - } - - _activityLists[ast] = list; - - emit activityJobStatusCode(ast, statusCode); - - combineActivityLists(); -} - - -void ActivityListModel::combineActivityLists() -{ - ActivityList resultList; - - foreach( ActivityList list, _activityLists.values() ) { - resultList.append(list); - } - - std::sort( resultList.begin(), resultList.end() ); - - beginInsertRows(QModelIndex(), 0, resultList.count()-1); - _finalList = resultList; - endInsertRows(); -} - -void ActivityListModel::fetchMore(const QModelIndex &) -{ - QList accounts = AccountManager::instance()->accounts(); - - foreach (AccountStatePtr asp, accounts) { - bool newItem = false; - - if( !_activityLists.contains(asp.data()) && asp->isConnected() ) { - _activityLists[asp.data()] = ActivityList(); - newItem = true; - } - if( newItem ) { - startFetchJob(asp.data()); - } - } -} - -void ActivityListModel::slotRefreshActivity(AccountState *ast) -{ - if(ast && _activityLists.contains(ast)) { - qDebug() << "**** Refreshing Activity list for" << ast->account()->displayName(); - _activityLists.remove(ast); - } - startFetchJob(ast); -} - -void ActivityListModel::slotRemoveAccount(AccountState *ast ) -{ - if( _activityLists.contains(ast) ) { - int i = 0; - const QString accountToRemove = ast->account()->displayName(); - - QMutableListIterator it(_finalList); - - while (it.hasNext()) { - Activity activity = it.next(); - if( activity._accName == accountToRemove ) { - beginRemoveRows(QModelIndex(), i, i+1); - it.remove(); - endRemoveRows(); - } - } - _activityLists.remove(ast); - _currentlyFetching.remove(ast); - } -} /* ==================================================================== */ diff --git a/src/gui/activitywidget.h b/src/gui/activitywidget.h index 62ef46e0c..a2d5c8116 100644 --- a/src/gui/activitywidget.h +++ b/src/gui/activitywidget.h @@ -22,6 +22,7 @@ #include "progressdispatcher.h" #include "owncloudgui.h" #include "account.h" +#include "activitydata.h" #include "ui_activitywidget.h" @@ -35,120 +36,13 @@ class AccountStatusPtr; class ProtocolWidget; class JsonApiJob; class NotificationWidget; +class ActivityListModel; namespace Ui { class ActivityWidget; } class Application; -/** - * @brief The ActivityLink class describes actions of an activity - * - * These are part of notifications which are mapped into activities. - */ - -class ActivityLink -{ -public: - QHash toVariantHash(); - - QString _label; - QString _link; - QString _verb; - bool _isPrimary; -}; - -/** - * @brief Activity Structure - * @ingroup gui - * - * contains all the information describing a single activity. - */ - -class Activity -{ -public: - enum Type { - ActivityType, - NotificationType - }; - Type _type; - qlonglong _id; - QString _subject; - QString _message; - QString _file; - QUrl _link; - QDateTime _dateTime; - QString _accName; - - QVector _links; - /** - * @brief Sort operator to sort the list youngest first. - * @param val - * @return - */ - bool operator<( const Activity& val ) const { - return _dateTime.toMSecsSinceEpoch() > val._dateTime.toMSecsSinceEpoch(); - } - -}; - -/** - * @brief The ActivityList - * @ingroup gui - * - * A QList based list of Activities - */ -class ActivityList:public QList -{ -public: - void setAccountName( const QString& name ); - QString accountName() const; - -private: - QString _accountName; -}; - - -/** - * @brief The ActivityListModel - * @ingroup gui - * - * Simple list model to provide the list view with data. - */ -class ActivityListModel : public QAbstractListModel -{ - Q_OBJECT -public: - explicit ActivityListModel(QWidget *parent=0); - - QVariant data(const QModelIndex &index, int role) const Q_DECL_OVERRIDE; - int rowCount(const QModelIndex& parent = QModelIndex()) const Q_DECL_OVERRIDE; - - bool canFetchMore(const QModelIndex& ) const Q_DECL_OVERRIDE; - void fetchMore(const QModelIndex&) Q_DECL_OVERRIDE; - - ActivityList activityList() { return _finalList; } - -public slots: - void slotRefreshActivity(AccountState* ast); - void slotRemoveAccount( AccountState *ast ); - -private slots: - void slotActivitiesReceived(const QVariantMap& json, int statusCode); - -signals: - void activityJobStatusCode(AccountState* ast, int statusCode); - -private: - void startFetchJob(AccountState* s); - void combineActivityLists(); - - QMap _activityLists; - ActivityList _finalList; - QSet _currentlyFetching; -}; - /** * @brief The ActivityWidget class * @ingroup gui diff --git a/src/gui/notificationwidget.h b/src/gui/notificationwidget.h index c18374220..89f3d2043 100644 --- a/src/gui/notificationwidget.h +++ b/src/gui/notificationwidget.h @@ -16,10 +16,12 @@ #include -#include "activitywidget.h" +#include "activitydata.h" #include "ui_notificationwidget.h" +class QProgressIndicator; + namespace OCC { class NotificationWidget : public QWidget