diff --git a/src/3rdparty/qtmacgoodies b/src/3rdparty/qtmacgoodies index 044580c3283..4ffbff5d5fc 160000 --- a/src/3rdparty/qtmacgoodies +++ b/src/3rdparty/qtmacgoodies @@ -1 +1 @@ -Subproject commit 044580c32837edba05a055aabca27245939454eb +Subproject commit 4ffbff5d5fca7332f6390ddc2fe74cd29e8675f8 diff --git a/src/gui/settingsdialog.cpp b/src/gui/settingsdialog.cpp index 499fb3e0afa..0deab368752 100644 --- a/src/gui/settingsdialog.cpp +++ b/src/gui/settingsdialog.cpp @@ -40,6 +40,8 @@ #include #include #include +#include +#include namespace { const char TOOLBAR_CSS[] = @@ -54,6 +56,23 @@ namespace { namespace OCC { +static QIcon circleMask( const QImage& avatar ) +{ + int dim = avatar.width(); + + QPixmap fixedImage(dim, dim); + fixedImage.fill(Qt::transparent); + + QPainter imgPainter(&fixedImage); + QPainterPath clip; + clip.addEllipse(0, 0, dim, dim); + imgPainter.setClipPath(clip); + imgPainter.drawImage(0, 0, avatar); + imgPainter.end(); + + return QIcon(fixedImage); +} + // // Whenever you change something here check both settingsdialog.cpp and settingsdialogmac.cpp ! // @@ -196,8 +215,17 @@ void SettingsDialog::accountAdded(AccountState *s) bool brandingSingleAccount = !Theme::instance()->multiAccount(); - auto accountAction = createColorAwareAction(QLatin1String(":/client/resources/account.png"), - brandingSingleAccount ? tr("Account") : s->account()->displayName()); + QAction *accountAction; + QImage avatar = s->account()->avatar(); + const QString actionText = brandingSingleAccount ? tr("Account") : s->account()->displayName(); + if(avatar.isNull()) { + accountAction = createColorAwareAction(QLatin1String(":/client/resources/account.png"), + actionText); + } else { + QIcon icon = circleMask(avatar); + accountAction = createActionWithIcon(icon, actionText); + } + if (!brandingSingleAccount) { accountAction->setToolTip(s->account()->displayName()); accountAction->setIconText(s->shortDisplayNameForSettings(height * buttonSizeRatio)); @@ -207,14 +235,30 @@ void SettingsDialog::accountAdded(AccountState *s) _ui->stack->insertWidget(0 , accountSettings); _actionGroup->addAction(accountAction); _actionGroupWidgets.insert(accountAction, accountSettings); + _actionForAccount.insert(s->account().data(), accountAction); connect( accountSettings, SIGNAL(folderChanged()), _gui, SLOT(slotFoldersChanged())); connect( accountSettings, SIGNAL(openFolderAlias(const QString&)), _gui, SLOT(slotFolderOpenAction(QString))); + connect(s->account().data(), SIGNAL(accountChangedAvatar()), SLOT(slotAccountAvatarChanged())); slotRefreshActivity(s); } +void SettingsDialog::slotAccountAvatarChanged() +{ + Account *account = static_cast(sender()); + if( account && _actionForAccount.contains(account)) { + QAction *action = _actionForAccount[account]; + if( action ) { + QImage pix = account->avatar(); + if( !pix.isNull() ) { + action->setIcon( circleMask(pix) ); + } + } + } +} + void SettingsDialog::accountRemoved(AccountState *s) { for (auto it = _actionGroupWidgets.begin(); it != _actionGroupWidgets.end(); ++it) { @@ -236,6 +280,9 @@ void SettingsDialog::accountRemoved(AccountState *s) } } + if( _actionForAccount.contains(s->account().data()) ) { + _actionForAccount.remove(s->account().data()); + } _activitySettings->slotRemoveAccount(s); // Hide when the last account is deleted. We want to enter the same @@ -306,14 +353,22 @@ class ToolButtonAction : public QWidgetAction } }; +QAction *SettingsDialog::createActionWithIcon(const QIcon& icon, const QString& text, const QString& iconPath) +{ + QAction *action = new ToolButtonAction(icon, text, this); + action->setCheckable(true); + if(!iconPath.isEmpty()) { + action->setProperty("iconPath", iconPath); + } + return action; + +} + QAction *SettingsDialog::createColorAwareAction(const QString &iconPath, const QString &text) { // all buttons must have the same size in order to keep a good layout QIcon coloredIcon = createColorAwareIcon(iconPath); - QAction *action = new ToolButtonAction(coloredIcon, text, this); - action->setCheckable(true); - action->setProperty("iconPath", iconPath); - return action; + return createActionWithIcon(coloredIcon, text, iconPath); } void SettingsDialog::slotRefreshActivity( AccountState* accountState ) diff --git a/src/gui/settingsdialog.h b/src/gui/settingsdialog.h index dd4ffd44842..39f2595a1ae 100644 --- a/src/gui/settingsdialog.h +++ b/src/gui/settingsdialog.h @@ -58,6 +58,7 @@ public slots: void showActivityPage(); void slotSwitchPage(QAction *action); void slotRefreshActivity(AccountState *accountState ); + void slotAccountAvatarChanged(); protected: void reject() Q_DECL_OVERRIDE; @@ -73,12 +74,18 @@ private slots: QIcon createColorAwareIcon(const QString &name); QAction *createColorAwareAction(const QString &iconName, const QString &fileName); + QAction *createActionWithIcon(const QIcon& icon, const QString& text, const QString& iconPath = QString()); + Ui::SettingsDialog * const _ui; QActionGroup* _actionGroup; // Maps the actions from the action group to the corresponding widgets QHash _actionGroupWidgets; + // Maps the action in the dialog to their according account. Needed in + // case the account avatar changes + QHash _actionForAccount; + QToolBar* _toolBar; ActivitySettings *_activitySettings; diff --git a/src/gui/settingsdialogmac.cpp b/src/gui/settingsdialogmac.cpp index 9e4d6a950ee..ac65dae7a33 100644 --- a/src/gui/settingsdialogmac.cpp +++ b/src/gui/settingsdialogmac.cpp @@ -34,9 +34,30 @@ #include #include #include +#include +#include namespace OCC { +// Duplicate in settingsdialog.cpp +static QIcon circleMask( const QImage& avatar ) +{ + int dim = avatar.width(); + + QPixmap fixedImage(dim, dim); + fixedImage.fill(Qt::transparent); + + QPainter imgPainter(&fixedImage); + QPainterPath clip; + clip.addEllipse(0, 0, dim, dim); + imgPainter.setClipPath(clip); + imgPainter.drawImage(0, 0, avatar); + imgPainter.end(); + + return QIcon(fixedImage); +} + + // // Whenever you change something here check both settingsdialog.cpp and settingsdialogmac.cpp ! // @@ -125,6 +146,8 @@ void SettingsDialogMac::accountAdded(AccountState *s) connect( accountSettings, &AccountSettings::folderChanged, _gui, &ownCloudGui::slotFoldersChanged); connect( accountSettings, &AccountSettings::openFolderAlias, _gui, &ownCloudGui::slotFolderOpenAction); + connect(s->account().data(), SIGNAL(accountChangedAvatar()), this, SLOT(slotAccountAvatarChanged())); + slotRefreshActivity(s); } @@ -147,4 +170,19 @@ void SettingsDialogMac::slotRefreshActivity( AccountState* accountState ) } } +void SettingsDialogMac::slotAccountAvatarChanged() +{ + Account *account = static_cast(sender()); + auto list = findChildren(QString()); + foreach(auto p, list) { + if (p->accountsState()->account() == account) { + int idx = indexForPanel(p); + QImage pix = account->avatar(); + if (!pix.isNull()) { + setPreferencesPanelIcon(idx, circleMask(pix)); + } + } + } +} + } diff --git a/src/gui/settingsdialogmac.h b/src/gui/settingsdialogmac.h index 608c4f8e18d..a000461534e 100644 --- a/src/gui/settingsdialogmac.h +++ b/src/gui/settingsdialogmac.h @@ -52,6 +52,7 @@ public slots: private slots: void accountAdded(AccountState *); void accountRemoved(AccountState *); + void slotAccountAvatarChanged(); private: void closeEvent(QCloseEvent *event); diff --git a/src/libsync/account.cpp b/src/libsync/account.cpp index 77f953659a9..06634056d49 100644 --- a/src/libsync/account.cpp +++ b/src/libsync/account.cpp @@ -90,6 +90,16 @@ void Account::setDavUser(const QString &newDavUser) _davUser = newDavUser; } +QImage Account::avatar() const +{ + return _avatarImg; +} +void Account::setAvatar(const QImage &img) +{ + _avatarImg = img; + emit accountChangedAvatar(); +} + QString Account::displayName() const { QString dn = QString("%1@%2").arg(_credentials->user(), _url.host()); diff --git a/src/libsync/account.h b/src/libsync/account.h index 2c21fa548f7..4b61efb2ee5 100644 --- a/src/libsync/account.h +++ b/src/libsync/account.h @@ -26,6 +26,8 @@ #include #include #include +#include + #include "utility.h" #include #include "capabilities.h" @@ -78,6 +80,9 @@ class OWNCLOUDSYNC_EXPORT Account : public QObject { QString davUser() const; void setDavUser(const QString &newDavUser); + QImage avatar() const; + void setAvatar(const QImage& img); + /// The name of the account as shown in the toolbar QString displayName() const; @@ -197,6 +202,8 @@ class OWNCLOUDSYNC_EXPORT Account : public QObject { void serverVersionChanged(Account* account, const QString& newVersion, const QString& oldVersion); + void accountChangedAvatar(); + protected Q_SLOTS: void slotHandleSslErrors(QNetworkReply*,QList); void slotCredentialsFetched(); @@ -209,6 +216,7 @@ protected Q_SLOTS: QWeakPointer _sharedThis; QString _id; QString _davUser; + QImage _avatarImg; QMap _settingsMap; QUrl _url; QList _approvedCerts; diff --git a/src/libsync/connectionvalidator.cpp b/src/libsync/connectionvalidator.cpp index ec63685aa4c..7e63bb538bf 100644 --- a/src/libsync/connectionvalidator.cpp +++ b/src/libsync/connectionvalidator.cpp @@ -15,6 +15,7 @@ #include #include #include +#include #include "connectionvalidator.h" #include "account.h" @@ -252,7 +253,18 @@ void ConnectionValidator::slotUserFetched(const QVariantMap &json) QString user = json.value("ocs").toMap().value("data").toMap().value("id").toString(); if (!user.isEmpty()) { _account->setDavUser(user); + + AvatarJob *job = new AvatarJob(_account, this); + job->setTimeout(20*1000); + QObject::connect(job, SIGNAL(avatarPixmap(QImage)), this, SLOT(slotAvatarImage(QImage))); + + job->start(); } +} + +void ConnectionValidator::slotAvatarImage(const QImage& img) +{ + _account->setAvatar(img); reportResult(Connected); } diff --git a/src/libsync/connectionvalidator.h b/src/libsync/connectionvalidator.h index 13e1435a5fe..a612b61822d 100644 --- a/src/libsync/connectionvalidator.h +++ b/src/libsync/connectionvalidator.h @@ -68,7 +68,10 @@ namespace OCC { +-> fetchUser PropfindJob | - +-> slotUserFetched --> X + +-> slotUserFetched + AvatarJob + | + +-> slotAvatarImage --> reportResult() \endcode */ @@ -119,6 +122,7 @@ protected slots: void slotCapabilitiesRecieved(const QVariantMap&); void slotUserFetched(const QVariantMap &); + void slotAvatarImage(const QImage &img); private: void reportResult(Status status); diff --git a/src/libsync/networkjobs.cpp b/src/libsync/networkjobs.cpp index 4cc5801ae7c..f89da5e6a5c 100644 --- a/src/libsync/networkjobs.cpp +++ b/src/libsync/networkjobs.cpp @@ -27,6 +27,7 @@ #include #include #include +#include #include "json.h" @@ -589,6 +590,42 @@ bool PropfindJob::finished() /*********************************************************************************************/ +AvatarJob::AvatarJob(AccountPtr account, QObject *parent) + : AbstractNetworkJob(account, QString(), parent) +{ + _avatarUrl = Utility::concatUrlPath(account->url(), QString("remote.php/dav/avatars/%1/128.png").arg(account->davUser())); +} + +void AvatarJob::start() +{ + QNetworkRequest req; + setReply(davRequest("GET", _avatarUrl, req)); + setupConnections(reply()); + AbstractNetworkJob::start(); +} + +bool AvatarJob::finished() +{ + int http_result_code = reply()->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt(); + + QImage avImage; + + if (http_result_code == 200) { + + QByteArray pngData = reply()->readAll(); + if( pngData.size() ) { + + if( avImage.loadFromData(pngData) ) { + qDebug() << "Retrieved Avatar pixmap!"; + } + } + } + emit(avatarPixmap(avImage)); + return true; +} + +/*********************************************************************************************/ + ProppatchJob::ProppatchJob(AccountPtr account, const QString &path, QObject *parent) : AbstractNetworkJob(account, path, parent) { diff --git a/src/libsync/networkjobs.h b/src/libsync/networkjobs.h index f7a37d95004..6802c5d2bfc 100644 --- a/src/libsync/networkjobs.h +++ b/src/libsync/networkjobs.h @@ -129,6 +129,36 @@ private slots: QList _properties; }; + +/** + * @brief The AvatarJob class + * + * Retrieves the account users avatar from the server using a GET request. + * + * If the server does not have the avatar, the result Pixmap is empty. + * + * @ingroup libsync + */ +class OWNCLOUDSYNC_EXPORT AvatarJob : public AbstractNetworkJob { + Q_OBJECT +public: + explicit AvatarJob(AccountPtr account, QObject *parent = 0); + void start() Q_DECL_OVERRIDE; + +signals: + /** + * @brief avatarPixmap - returns either a valid pixmap or not. + */ + + void avatarPixmap(QImage); + +private slots: + virtual bool finished() Q_DECL_OVERRIDE; + +private: + QUrl _avatarUrl; +}; + /** * @brief Send a Proppatch request *