From b32310b8a6b298e7bc96df14205e00d8bd1f8c00 Mon Sep 17 00:00:00 2001 From: Dominique Fuchs <32204802+DominiqueFuchs@users.noreply.github.com> Date: Fri, 3 Jan 2020 13:09:29 +0100 Subject: [PATCH] Gigantic ton of changes and deletions: ActivityListModel, tray GUI, Account logic. Signed-off-by: Dominique Fuchs <32204802+DominiqueFuchs@users.noreply.github.com> --- src/gui/CMakeLists.txt | 10 +- src/gui/accountsettings.ui | 2 +- src/gui/accountstate.cpp | 25 +- src/gui/accountstate.h | 4 + src/gui/activityitemdelegate.cpp | 407 ----------- src/gui/activityitemdelegate.h | 94 --- src/gui/activitywidget.cpp | 653 ------------------ src/gui/activitywidget.h | 165 ----- src/gui/activitywidget.ui | 98 --- src/gui/generalsettings.ui | 2 +- src/gui/networksettings.ui | 2 +- src/gui/owncloudgui.cpp | 11 - src/gui/servernotificationhandler.cpp | 165 ----- src/gui/servernotificationhandler.h | 50 -- src/gui/settingsdialog.cpp | 72 +- src/gui/settingsdialog.h | 6 - src/gui/settingsdialog.ui | 2 +- src/gui/systray.cpp | 17 +- src/gui/systray.h | 1 + .../ActivityData.cpp} | 0 .../{activitydata.h => tray/ActivityData.h} | 0 .../ActivityListModel.cpp} | 37 +- .../ActivityListModel.h} | 17 +- src/gui/tray/UserLine.qml | 7 +- src/gui/tray/UserModel.cpp | 99 +-- src/gui/tray/UserModel.h | 42 +- src/gui/tray/window.qml | 26 +- src/libsync/capabilities.cpp | 3 +- 28 files changed, 139 insertions(+), 1878 deletions(-) delete mode 100644 src/gui/activityitemdelegate.cpp delete mode 100644 src/gui/activityitemdelegate.h delete mode 100644 src/gui/activitywidget.cpp delete mode 100644 src/gui/activitywidget.h delete mode 100644 src/gui/activitywidget.ui delete mode 100644 src/gui/servernotificationhandler.cpp delete mode 100644 src/gui/servernotificationhandler.h rename src/gui/{activitydata.cpp => tray/ActivityData.cpp} (100%) rename src/gui/{activitydata.h => tray/ActivityData.h} (100%) rename src/gui/{activitylistmodel.cpp => tray/ActivityListModel.cpp} (93%) rename src/gui/{activitylistmodel.h => tray/ActivityListModel.h} (88%) diff --git a/src/gui/CMakeLists.txt b/src/gui/CMakeLists.txt index 9b0455a6b..af364ca4e 100644 --- a/src/gui/CMakeLists.txt +++ b/src/gui/CMakeLists.txt @@ -24,7 +24,6 @@ set(client_UI_SRCS ignorelisteditor.ui ignorelisttablewidget.ui networksettings.ui - activitywidget.ui synclogdialog.ui settingsdialog.ui sharedialog.ui @@ -35,7 +34,7 @@ set(client_UI_SRCS addcertificatedialog.ui proxyauthdialog.ui mnemonicdialog.ui - tray/window.qml + tray/Window.qml tray/UserLine.qml wizard/flow2authwidget.ui wizard/owncloudadvancedsetuppage.ui @@ -75,10 +74,6 @@ set(client_SRCS openfilemanager.cpp owncloudgui.cpp owncloudsetupwizard.cpp - activitydata.cpp - activitylistmodel.cpp - activitywidget.cpp - activityitemdelegate.cpp selectivesyncdialog.cpp settingsdialog.cpp sharedialog.cpp @@ -101,12 +96,13 @@ set(client_SRCS synclogdialog.cpp tooltipupdater.cpp notificationconfirmjob.cpp - servernotificationhandler.cpp guiutility.cpp elidedlabel.cpp headerbanner.cpp iconjob.cpp remotewipe.cpp + tray/ActivityData.cpp + tray/ActivityListModel.cpp tray/UserModel.cpp creds/credentialsfactory.cpp creds/httpcredentialsgui.cpp diff --git a/src/gui/accountsettings.ui b/src/gui/accountsettings.ui index f52adf4c7..6249018bd 100644 --- a/src/gui/accountsettings.ui +++ b/src/gui/accountsettings.ui @@ -6,7 +6,7 @@ 0 0 - 582 + 516 557 diff --git a/src/gui/accountstate.cpp b/src/gui/accountstate.cpp index 7893a7a3b..9d2fa1830 100644 --- a/src/gui/accountstate.cpp +++ b/src/gui/accountstate.cpp @@ -74,6 +74,11 @@ AccountPtr AccountState::account() const return _account; } +bool AccountState::hasTalk() const +{ + return _hasTalk; +} + AccountState::ConnectionStatus AccountState::connectionStatus() const { return _connectionStatus; @@ -89,6 +94,23 @@ AccountState::State AccountState::state() const return _state; } +void AccountState::setTalkCapability() +{ + QNetworkAccessManager *manager = new QNetworkAccessManager(this); + connect(manager, &QNetworkAccessManager::finished, + this, [=](QNetworkReply *reply) { + if (reply->error()) { + return; + } + //TODO: This is **** dirty, but atm I don't see a capability-way to determine talk integration + _hasTalk = (reply->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt() == 303) ? true : false; + }); + QNetworkRequest request; + QString url = this->account()->url().toString() + "/apps/spreed"; + request.setUrl(QUrl(url)); + manager->get(request); +} + void AccountState::setState(State state) { if (_state != state) { @@ -267,7 +289,7 @@ void AccountState::slotConnectionValidatorResult(ConnectionValidator::Status sta // Come online gradually from 503 or maintenance mode if (status == ConnectionValidator::Connected && (_connectionStatus == ConnectionValidator::ServiceUnavailable - || _connectionStatus == ConnectionValidator::MaintenanceMode)) { + || _connectionStatus == ConnectionValidator::MaintenanceMode)) { if (!_timeSinceMaintenanceOver.isValid()) { qCInfo(lcAccountState) << "AccountState reconnection: delaying for" << _maintenanceToConnectedDelay << "ms"; @@ -293,6 +315,7 @@ void AccountState::slotConnectionValidatorResult(ConnectionValidator::Status sta case ConnectionValidator::Connected: if (_state != Connected) { setState(Connected); + setTalkCapability(); } break; case ConnectionValidator::Undefined: diff --git a/src/gui/accountstate.h b/src/gui/accountstate.h index 6ed9aa265..7633bd78d 100644 --- a/src/gui/accountstate.h +++ b/src/gui/accountstate.h @@ -101,6 +101,8 @@ public: bool isSignedOut() const; + bool hasTalk() const; + /** A user-triggered sign out which disconnects, stops syncs * for the account and forgets the password. */ void signOutByUi(); @@ -161,6 +163,7 @@ public slots: private: void setState(State state); + void setTalkCapability(); signals: void stateChanged(int state); @@ -182,6 +185,7 @@ private: ConnectionStatus _connectionStatus; QStringList _connectionErrors; bool _waitingForNewCredentials; + bool _hasTalk; QElapsedTimer _timeSinceLastETagCheck; QPointer _connectionValidator; QByteArray _notificationsEtagResponseHeader; diff --git a/src/gui/activityitemdelegate.cpp b/src/gui/activityitemdelegate.cpp deleted file mode 100644 index 2b4231e07..000000000 --- a/src/gui/activityitemdelegate.cpp +++ /dev/null @@ -1,407 +0,0 @@ -/* - * Copyright (C) by Klaas Freitag - * Copyright (C) by Olivier Goffart - * - * - * 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; either version 2 of the License, or - * (at your option) any later version. - * - * 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 "activityitemdelegate.h" -#include "activitylistmodel.h" -#include "folderstatusmodel.h" -#include "folderman.h" -#include "accountstate.h" -#include "activitydata.h" -#include -#include - -#include -#include -#include - -#define FIXME_USE_HIGH_DPI_RATIO -#ifdef FIXME_USE_HIGH_DPI_RATIO - // FIXME: Find a better way to calculate the text width on high-dpi displays (Retina). - #include -#endif - -#define HASQT5_11 (QT_VERSION >= QT_VERSION_CHECK(5,11,0)) - -namespace OCC { - -int ActivityItemDelegate::_iconHeight = 0; -int ActivityItemDelegate::_margin = 0; -int ActivityItemDelegate::_primaryButtonWidth = 0; -int ActivityItemDelegate::_secondaryButtonWidth = 0; -int ActivityItemDelegate::_spaceBetweenButtons = 0; -int ActivityItemDelegate::_timeWidth = 0; -int ActivityItemDelegate::_buttonHeight = 0; -const QString ActivityItemDelegate::_remote_share("remote_share"); -const QString ActivityItemDelegate::_call("call"); - -ActivityItemDelegate::ActivityItemDelegate() - : QStyledItemDelegate() -{ - customizeStyle(); -} - -int ActivityItemDelegate::iconHeight() -{ - if (_iconHeight == 0) { - QStyleOptionViewItem option; - QFont font = option.font; - - QFontMetrics fm(font); - - _iconHeight = qRound(fm.height() / 5.0 * 8.0); - } - return _iconHeight; -} - -int ActivityItemDelegate::rowHeight() -{ - if (_margin == 0) { - QStyleOptionViewItem opt; - - QFont f = opt.font; - QFontMetrics fm(f); - - _margin = fm.height() / 2; - -#if defined(Q_OS_WIN) - _margin += 5; -#endif - } - return iconHeight() + 5 * _margin; -} - -QSize ActivityItemDelegate::sizeHint(const QStyleOptionViewItem &option, - const QModelIndex & /* index */) const -{ - QFont font = option.font; - - return QSize(0, rowHeight()); -} - -void ActivityItemDelegate::paint(QPainter *painter, const QStyleOptionViewItem &option, - const QModelIndex &index) const -{ - QStyledItemDelegate::paint(painter, option, index); - QFont font = option.font; - QFontMetrics fm(font); - int margin = fm.height() / 2.5; - painter->save(); - int iconSize = 16; - int iconOffset = qRound(fm.height() / 4.0 * 7.0); - int offset = 4; - const bool isSelected = (option.state & QStyle::State_Selected); -#ifdef FIXME_USE_HIGH_DPI_RATIO - // FIXME: Find a better way to calculate the text width on high-dpi displays (Retina). - const int device_pixel_ration = QApplication::desktop()->devicePixelRatio(); - int pixel_ratio = (device_pixel_ration > 1 ? device_pixel_ration : 1); -#endif - - // get the data - Activity::Type activityType = qvariant_cast(index.data(ActionRole)); - QIcon actionIcon; - const ActivityListModel::ActionIcon icn = qvariant_cast(index.data(ActionIconRole)); - switch(icn.iconType) { - case ActivityListModel::ActivityIconType::iconUseCached: actionIcon = icn.cachedIcon; break; - case ActivityListModel::ActivityIconType::iconActivity: actionIcon = (isSelected ? _iconActivity_sel : _iconActivity); break; - case ActivityListModel::ActivityIconType::iconBell: actionIcon = (isSelected ? _iconBell_sel : _iconBell); break; - case ActivityListModel::ActivityIconType::iconStateError: actionIcon = _iconStateError; break; - case ActivityListModel::ActivityIconType::iconStateWarning: actionIcon = _iconStateWarning; break; - case ActivityListModel::ActivityIconType::iconStateInfo: actionIcon = _iconStateInfo; break; - case ActivityListModel::ActivityIconType::iconStateSync: actionIcon = _iconStateSync; break; - } - QString objectType = qvariant_cast(index.data(ObjectTypeRole)); - QString actionText = qvariant_cast(index.data(ActionTextRole)); - QString messageText = qvariant_cast(index.data(MessageRole)); - QList customList = index.data(ActionsLinksRole).toList(); - QString timeText = qvariant_cast(index.data(PointInTimeRole)); - bool accountOnline = qvariant_cast(index.data(AccountConnectedRole)); - - // activity/notification icons - QRect actionIconRect = option.rect; - actionIconRect.setLeft(option.rect.left() + iconOffset/3); - actionIconRect.setRight(option.rect.left() + iconOffset); - actionIconRect.setTop(option.rect.top() + qRound((option.rect.height() - 16)/3.0)); - - // subject text rect - QRect actionTextBox = actionIconRect; -#if (HASQT5_11) - int actionTextBoxWidth = fm.horizontalAdvance(actionText); -#else - int actionTextBoxWidth = fm.width(actionText); -#endif - actionTextBox.setTop(option.rect.top() + margin + offset/2); - actionTextBox.setHeight(fm.height()); - actionTextBox.setLeft(actionIconRect.right() + margin); -#ifdef FIXME_USE_HIGH_DPI_RATIO - // FIXME: Find a better way to calculate the text width on high-dpi displays (Retina). - actionTextBoxWidth *= pixel_ratio; -#endif - actionTextBox.setRight(actionTextBox.left() + actionTextBoxWidth + margin); - - // message text rect - QRect messageTextBox = actionTextBox; -#if (HASQT5_11) - int messageTextWidth = fm.horizontalAdvance(messageText); -#else - int messageTextWidth = fm.width(messageText); -#endif - int messageTextTop = option.rect.top() + fm.height() + margin; - if(actionText.isEmpty()) messageTextTop = option.rect.top() + margin + offset/2; - messageTextBox.setTop(messageTextTop); - messageTextBox.setHeight(fm.height()); - messageTextBox.setBottom(messageTextBox.top() + fm.height()); - messageTextBox.setRight(messageTextBox.left() + messageTextWidth + margin); - if(messageText.isEmpty()){ - messageTextBox.setHeight(0); - messageTextBox.setBottom(messageTextBox.top()); - } - - // time box rect - QRect timeBox = messageTextBox; -#if (HASQT5_11) - int timeTextWidth = fm.horizontalAdvance(timeText); -#else - int timeTextWidth = fm.width(timeText); -#endif - int timeTop = option.rect.top() + fm.height() + fm.height() + margin + offset/2; - if(messageText.isEmpty() || actionText.isEmpty()) - timeTop = option.rect.top() + fm.height() + margin; - timeBox.setTop(timeTop); - timeBox.setHeight(fm.height()); - timeBox.setBottom(timeBox.top() + fm.height()); -#ifdef FIXME_USE_HIGH_DPI_RATIO - // FIXME: Find a better way to calculate the text width on high-dpi displays (Retina). - timeTextWidth *= pixel_ratio; -#endif - timeBox.setRight(timeBox.left() + timeTextWidth + margin); - - // buttons - default values - int rightMargin = margin; - int leftMargin = margin * offset; - int top = option.rect.top() + margin; - int buttonSize = option.rect.height()/2; - int right = option.rect.right() - rightMargin; - int left = right - buttonSize; - - QStyleOptionButton secondaryButton; - secondaryButton.rect = option.rect; - secondaryButton.features |= QStyleOptionButton::Flat; - secondaryButton.state |= QStyle::State_None; - secondaryButton.rect.setLeft(left); - secondaryButton.rect.setRight(right); - secondaryButton.rect.setTop(top + margin); - secondaryButton.rect.setHeight(iconSize); - - QStyleOptionButton primaryButton; - primaryButton.rect = option.rect; - primaryButton.features |= QStyleOptionButton::DefaultButton; - primaryButton.state |= QStyle::State_Raised; - primaryButton.rect.setTop(top); - primaryButton.rect.setHeight(buttonSize); - - right = secondaryButton.rect.left() - rightMargin; - left = secondaryButton.rect.left() - leftMargin; - - primaryButton.rect.setRight(right); - - if(activityType == Activity::Type::NotificationType){ - - // Secondary will be 'Dismiss' or '...' multiple options button - secondaryButton.icon = (isSelected ? _iconClose_sel : _iconClose); - if(customList.size() > 1) - secondaryButton.icon = (isSelected ? _iconMore_sel : _iconMore); - secondaryButton.iconSize = QSize(iconSize, iconSize); - - // Primary button will be 'More Information' or 'Accept' - primaryButton.text = tr("More information"); - if(objectType == _remote_share) primaryButton.text = tr("Accept"); - if(objectType == _call) primaryButton.text = tr("Join"); - -#if (HASQT5_11) - primaryButton.rect.setLeft(left - margin * 2 - fm.horizontalAdvance(primaryButton.text)); -#else - primaryButton.rect.setLeft(left - margin * 2 - fm.width(primaryButton.text)); -#endif - - // save info to be able to filter mouse clicks - _buttonHeight = buttonSize; - _primaryButtonWidth = primaryButton.rect.size().width(); - _secondaryButtonWidth = secondaryButton.rect.size().width(); - _spaceBetweenButtons = secondaryButton.rect.left() - primaryButton.rect.right(); - - } else if(activityType == Activity::SyncResultType){ - - // Secondary will be 'open file manager' with the folder icon - secondaryButton.icon = _iconFolder; - secondaryButton.iconSize = QSize(iconSize, iconSize); - - // Primary button will be 'open browser' - primaryButton.text = tr("Open Browser"); - -#if (HASQT5_11) - primaryButton.rect.setLeft(left - margin * 2 - fm.horizontalAdvance(primaryButton.text)); -#else - primaryButton.rect.setLeft(left - margin * 2 - fm.width(primaryButton.text)); -#endif - - // save info to be able to filter mouse clicks - _buttonHeight = buttonSize; - _primaryButtonWidth = primaryButton.rect.size().width(); - _secondaryButtonWidth = secondaryButton.rect.size().width(); - _spaceBetweenButtons = secondaryButton.rect.left() - primaryButton.rect.right(); - - } else if(activityType == Activity::SyncFileItemType){ - - // Secondary will be 'open file manager' with the folder icon - secondaryButton.icon = _iconFolder; - secondaryButton.iconSize = QSize(iconSize, iconSize); - - // No primary button on this case - // Whatever error we have at this case it is local, there is no point on opening the browser - _primaryButtonWidth = 0; - _secondaryButtonWidth = secondaryButton.rect.size().width(); - _spaceBetweenButtons = secondaryButton.rect.left() - primaryButton.rect.right(); - - } else { - _spaceBetweenButtons = leftMargin; - _primaryButtonWidth = 0; - _secondaryButtonWidth = 0; - } - - // draw the icon - QPixmap pm = actionIcon.pixmap(iconSize, iconSize, QIcon::Normal); - painter->drawPixmap(QPoint(actionIconRect.left(), actionIconRect.top()), pm); - - // change pen color if use is not online - QPalette p = option.palette; - if(!accountOnline) - p.setCurrentColorGroup(QPalette::Disabled); - - // change pen color if the line is selected - if (isSelected) - painter->setPen(p.color(QPalette::HighlightedText)); - else - painter->setPen(p.color(QPalette::Text)); - - // calculate space for text - use the max possible before using the elipses - int spaceLeftForText = option.rect.width() - (actionIconRect.width() + margin + rightMargin + leftMargin) - - (_primaryButtonWidth + _secondaryButtonWidth + _spaceBetweenButtons); - - // draw the subject - const QString elidedAction = fm.elidedText(actionText, Qt::ElideRight, spaceLeftForText); - painter->drawText(actionTextBox, elidedAction); - - // draw the buttons - if(activityType == Activity::Type::NotificationType || activityType == Activity::Type::SyncResultType) { - primaryButton.palette = p; - if (isSelected) - primaryButton.palette.setColor(QPalette::ButtonText, p.color(QPalette::HighlightedText)); - else - primaryButton.palette.setColor(QPalette::ButtonText, p.color(QPalette::Text)); - - QApplication::style()->drawControl(QStyle::CE_PushButton, &primaryButton, painter); - } - - // Since they are errors on local syncing, there is nothing to do in the server - if(activityType != Activity::Type::ActivityType) - QApplication::style()->drawControl(QStyle::CE_PushButton, &secondaryButton, painter); - - // draw the message - // change pen color for the message - if(!messageText.isEmpty()){ - const QString elidedMessage = fm.elidedText(messageText, Qt::ElideRight, spaceLeftForText); - painter->drawText(messageTextBox, elidedMessage); - } - - // change pen color for the time - if (isSelected) - painter->setPen(p.color(QPalette::Disabled, QPalette::HighlightedText)); - else - painter->setPen(p.color(QPalette::Disabled, QPalette::Text)); - - // draw the time - const QString elidedTime = fm.elidedText(timeText, Qt::ElideRight, spaceLeftForText); - painter->drawText(timeBox, elidedTime); - - painter->restore(); -} - -bool ActivityItemDelegate::editorEvent(QEvent *event, QAbstractItemModel *model, - const QStyleOptionViewItem &option, const QModelIndex &index) -{ - Activity::Type activityType = qvariant_cast(index.data(ActionRole)); - if(activityType != Activity::Type::ActivityType){ - if (event->type() == QEvent::MouseButtonRelease){ - QMouseEvent *mouseEvent = (QMouseEvent*)event; - if(mouseEvent){ - int mouseEventX = mouseEvent->x(); - int mouseEventY = mouseEvent->y(); - int buttonsWidth = _primaryButtonWidth + _spaceBetweenButtons + _secondaryButtonWidth; - int x = option.rect.left() + option.rect.width() - buttonsWidth - _timeWidth; - int y = option.rect.top(); - - // clickable area for ... - if (mouseEventX > x && mouseEventX < x + buttonsWidth){ - if(mouseEventY > y && mouseEventY < y + _buttonHeight){ - - // ...primary button ('more information' or 'accept' on notifications or 'open browser' on errors) - if (mouseEventX > x && mouseEventX < x + _primaryButtonWidth){ - emit primaryButtonClickedOnItemView(index); - - // ...secondary button ('dismiss' on notifications or 'open file manager' on errors) - } else { - x += _primaryButtonWidth + _spaceBetweenButtons; - if (mouseEventX > x && mouseEventX < x + _secondaryButtonWidth) - emit secondaryButtonClickedOnItemView(index); - } - } - } - } - } - } - - return QStyledItemDelegate::editorEvent(event, model, option, index); -} - -void ActivityItemDelegate::slotStyleChanged() -{ - customizeStyle(); -} - -void ActivityItemDelegate::customizeStyle() -{ - QPalette pal; - pal.setColor(QPalette::Base, QColor(0,0,0)); // use dark background colour to invert icons - - _iconClose = Theme::createColorAwareIcon(QLatin1String(":/client/resources/close.svg")); - _iconClose_sel = Theme::createColorAwareIcon(QLatin1String(":/client/resources/close.svg"), pal); - _iconMore = Theme::createColorAwareIcon(QLatin1String(":/client/resources/more.svg")); - _iconMore_sel = Theme::createColorAwareIcon(QLatin1String(":/client/resources/more.svg"), pal); - - _iconFolder = QIcon(QLatin1String(":/client/resources/folder.svg")); - - _iconActivity = Theme::createColorAwareIcon(QLatin1String(":/client/resources/activity.png")); - _iconActivity_sel = Theme::createColorAwareIcon(QLatin1String(":/client/resources/activity.png"), pal); - _iconBell = Theme::createColorAwareIcon(QLatin1String(":/client/resources/bell.svg")); - _iconBell_sel = Theme::createColorAwareIcon(QLatin1String(":/client/resources/bell.svg"), pal); - - _iconStateError = QIcon(QLatin1String(":/client/resources/state-error.svg")); - _iconStateWarning = QIcon(QLatin1String(":/client/resources/state-warning.svg")); - _iconStateInfo = QIcon(QLatin1String(":/client/resources/state-info.svg")); - _iconStateSync = QIcon(QLatin1String(":/client/resources/state-sync.svg")); -} - -} // namespace OCC diff --git a/src/gui/activityitemdelegate.h b/src/gui/activityitemdelegate.h deleted file mode 100644 index 908889eee..000000000 --- a/src/gui/activityitemdelegate.h +++ /dev/null @@ -1,94 +0,0 @@ -/* - * Copyright (C) by Klaas Freitag - * Copyright (C) by Olivier Goffart - * - * 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; either version 2 of the License, or - * (at your option) any later version. - * - * 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. - */ - -#pragma once -#include -#include - -class QMouseEvent; - -namespace OCC { - -/** - * @brief The ActivityItemDelegate class - * @ingroup gui - */ -class ActivityItemDelegate : public QStyledItemDelegate -{ - Q_OBJECT -public: - enum datarole { ActionIconRole = Qt::UserRole + 1, - UserIconRole, - AccountRole, - ObjectTypeRole, - ActionsLinksRole, - ActionTextRole, - ActionRole, - MessageRole, - PathRole, - LinkRole, - PointInTimeRole, - AccountConnectedRole, - SyncFileStatusRole }; - - ActivityItemDelegate(); - - void paint(QPainter *, const QStyleOptionViewItem &, const QModelIndex &) const override; - QSize sizeHint(const QStyleOptionViewItem &, const QModelIndex &) const override; - bool editorEvent(QEvent *event, QAbstractItemModel *model, const QStyleOptionViewItem &option, - const QModelIndex &index) override; - - static int rowHeight(); - static int iconHeight(); - -public slots: - void slotStyleChanged(); - -signals: - void primaryButtonClickedOnItemView(const QModelIndex &index); - void secondaryButtonClickedOnItemView(const QModelIndex &index); - -private: - void customizeStyle(); - - static int _margin; - static int _iconHeight; - static int _primaryButtonWidth; - static int _secondaryButtonWidth; - static int _spaceBetweenButtons; - static int _timeWidth; - static int _buttonHeight; - static const QString _remote_share; - static const QString _call; - - QIcon _iconClose; - QIcon _iconClose_sel; - QIcon _iconMore; - QIcon _iconMore_sel; - - QIcon _iconFolder; - - QIcon _iconActivity; - QIcon _iconActivity_sel; - QIcon _iconBell; - QIcon _iconBell_sel; - - QIcon _iconStateError; - QIcon _iconStateWarning; - QIcon _iconStateInfo; - QIcon _iconStateSync; -}; - -} // namespace OCC diff --git a/src/gui/activitywidget.cpp b/src/gui/activitywidget.cpp deleted file mode 100644 index 469b0d14e..000000000 --- a/src/gui/activitywidget.cpp +++ /dev/null @@ -1,653 +0,0 @@ -/* - * 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; either version 2 of the License, or - * (at your option) any later version. - * - * 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 "activitylistmodel.h" -#include "activitywidget.h" -#include "syncresult.h" -#include "logger.h" -#include "theme.h" -#include "folderman.h" -#include "syncfileitem.h" -#include "folder.h" -#include "openfilemanager.h" -#include "owncloudpropagator.h" -#include "account.h" -#include "accountstate.h" -#include "accountmanager.h" -#include "activityitemdelegate.h" -#include "QProgressIndicator.h" -#include "notificationconfirmjob.h" -#include "servernotificationhandler.h" -#include "theme.h" -#include "ocsjob.h" -#include "configfile.h" -#include "guiutility.h" -#include "socketapi.h" -#include "ui_activitywidget.h" -#include "syncengine.h" - -#include - -// time span in milliseconds which has to be between two -// refreshes of the notifications -#define NOTIFICATION_REQUEST_FREE_PERIOD 15000 - -namespace OCC { - -ActivityWidget::ActivityWidget(AccountState *accountState, QWidget *parent) - : QWidget(parent) - , _ui(new Ui::ActivityWidget) - , _notificationRequestsRunning(0) - , _accountState(accountState) - , _accept(tr("Accept")) - , _remote_share("remote_share") -{ - _ui->setupUi(this); - -// Adjust copyToClipboard() when making changes here! -#if defined(Q_OS_MAC) - _ui->_activityList->setMinimumWidth(400); -#endif - - _model = new ActivityListModel(accountState, this); - ActivityItemDelegate *delegate = new ActivityItemDelegate; - delegate->setParent(this); - _ui->_activityList->setItemDelegate(delegate); - _ui->_activityList->setAlternatingRowColors(true); - _ui->_activityList->setModel(_model); - - showLabels(); - - connect(_model, &ActivityListModel::activityJobStatusCode, - this, &ActivityWidget::slotAccountActivityStatus); - - connect(_model, &QAbstractItemModel::rowsInserted, this, &ActivityWidget::rowsInserted); - - connect(delegate, &ActivityItemDelegate::primaryButtonClickedOnItemView, this, &ActivityWidget::slotPrimaryButtonClickedOnListView); - connect(delegate, &ActivityItemDelegate::secondaryButtonClickedOnItemView, this, &ActivityWidget::slotSecondaryButtonClickedOnListView); - connect(_ui->_activityList, &QListView::activated, this, &ActivityWidget::slotOpenFile); - - connect(ProgressDispatcher::instance(), &ProgressDispatcher::progressInfo, - this, &ActivityWidget::slotProgressInfo); - connect(ProgressDispatcher::instance(), &ProgressDispatcher::itemCompleted, - this, &ActivityWidget::slotItemCompleted); - connect(ProgressDispatcher::instance(), &ProgressDispatcher::syncError, - this, &ActivityWidget::addError); - - _removeTimer.setInterval(1000); - - // Connect styleChanged events to our widgets, so they can adapt (Dark-/Light-Mode switching) - connect(this, &ActivityWidget::styleChanged, delegate, &ActivityItemDelegate::slotStyleChanged); -} - -ActivityWidget::~ActivityWidget() -{ - delete _ui; -} - -void ActivityWidget::slotProgressInfo(const QString &folder, const ProgressInfo &progress) -{ - if (progress.status() == ProgressInfo::Reconcile) { - // Wipe all non-persistent entries - as well as the persistent ones - // in cases where a local discovery was done. - auto f = FolderMan::instance()->folder(folder); - if (!f) - return; - const auto &engine = f->syncEngine(); - const auto style = engine.lastLocalDiscoveryStyle(); - foreach (Activity activity, _model->errorsList()) { - if (activity._folder != folder){ - continue; - } - - if (style == LocalDiscoveryStyle::FilesystemOnly){ - _model->removeActivityFromActivityList(activity); - continue; - } - - if(activity._status == SyncFileItem::Conflict && !QFileInfo(f->path() + activity._file).exists()){ - _model->removeActivityFromActivityList(activity); - continue; - } - - if(activity._status == SyncFileItem::FileLocked && !QFileInfo(f->path() + activity._file).exists()){ - _model->removeActivityFromActivityList(activity); - continue; - } - - - if(activity._status == SyncFileItem::FileIgnored && !QFileInfo(f->path() + activity._file).exists()) { - _model->removeActivityFromActivityList(activity); - continue; - } - - - if(!QFileInfo(f->path() + activity._file).exists()){ - _model->removeActivityFromActivityList(activity); - continue; - } - - auto path = QFileInfo(activity._file).dir().path().toUtf8(); - if (path == ".") - path.clear(); - - if(engine.shouldDiscoverLocally(path)) - _model->removeActivityFromActivityList(activity); - } - - } - - if (progress.status() == ProgressInfo::Done) { - // We keep track very well of pending conflicts. - // Inform other components about them. - QStringList conflicts; - foreach (Activity activity, _model->errorsList()) { - if (activity._folder == folder - && activity._status == SyncFileItem::Conflict) { - conflicts.append(activity._file); - } - } - - emit ProgressDispatcher::instance()->folderConflicts(folder, conflicts); - } -} - -void ActivityWidget::slotItemCompleted(const QString &folder, const SyncFileItemPtr &item){ - auto folderInstance = FolderMan::instance()->folder(folder); - - if (!folderInstance) - return; - - // check if we are adding it to the right account and if it is useful information (protocol errors) - if(folderInstance->accountState() == _accountState){ - qCWarning(lcActivity) << "Item " << item->_file << " retrieved resulted in " << item->_errorString; - - Activity activity; - activity._type = Activity::SyncFileItemType; //client activity - activity._status = item->_status; - activity._dateTime = QDateTime::currentDateTime(); - activity._message = item->_originalFile; - activity._link = folderInstance->accountState()->account()->url(); - activity._accName = folderInstance->accountState()->account()->displayName(); - activity._file = item->_file; - activity._folder = folder; - - if(item->_status == SyncFileItem::NoStatus || item->_status == SyncFileItem::Success){ - qCWarning(lcActivity) << "Item " << item->_file << " retrieved successfully."; - activity._message.prepend(" "); - activity._message.prepend(tr("Synced")); - _model->addSyncFileItemToActivityList(activity); - } else { - qCWarning(lcActivity) << "Item " << item->_file << " retrieved resulted in error " << item->_errorString; - activity._subject = item->_errorString; - - if(item->_status == SyncFileItem::Status::FileIgnored) { - _model->addIgnoredFileToList(activity); - } else { - // add 'protocol error' to activity list - _model->addErrorToActivityList(activity); - } - } - } -} - -void ActivityWidget::addError(const QString &folderAlias, const QString &message, - ErrorCategory category) -{ - auto folderInstance = FolderMan::instance()->folder(folderAlias); - if (!folderInstance) - return; - - if(folderInstance->accountState() == _accountState){ - qCWarning(lcActivity) << "Item " << folderInstance->shortGuiLocalPath() << " retrieved resulted in " << message; - - Activity activity; - activity._type = Activity::SyncResultType; - activity._status = SyncResult::Error; - activity._dateTime = QDateTime::fromString(QDateTime::currentDateTime().toString(), Qt::ISODate); - activity._subject = message; - activity._message = folderInstance->shortGuiLocalPath(); - activity._link = folderInstance->shortGuiLocalPath(); - activity._accName = folderInstance->accountState()->account()->displayName(); - activity._folder = folderAlias; - - - if (category == ErrorCategory::InsufficientRemoteStorage) { - ActivityLink link; - link._label = tr("Retry all uploads"); - link._link = folderInstance->path(); - link._verb = ""; - link._isPrimary = true; - activity._links.append(link); - } - - // add 'other errors' to activity list - _model->addErrorToActivityList(activity); - } -} - - -void ActivityWidget::slotPrimaryButtonClickedOnListView(const QModelIndex &index){ - QUrl link = qvariant_cast(index.data(ActivityItemDelegate::LinkRole)); - QString objectType = index.data(ActivityItemDelegate::ObjectTypeRole).toString(); - if(!link.isEmpty()){ - qCWarning(lcActivity) << "Opening" << link.toString() << "in browser for Notification/Activity" << qvariant_cast(index.data(ActivityItemDelegate::ActionTextRole)); - Utility::openBrowser(link, this); - } else if(objectType == _remote_share){ - QVariant customItem = index.data(ActivityItemDelegate::ActionsLinksRole).toList().first(); - ActivityLink actionLink = qvariant_cast(customItem); - if(actionLink._label == _accept){ - qCWarning(lcActivity) << objectType << "action" << actionLink._label << "for" << qvariant_cast(index.data(ActivityItemDelegate::ActionTextRole)); - const QString accountName = index.data(ActivityItemDelegate::AccountRole).toString(); - slotSendNotificationRequest(accountName, actionLink._link, actionLink._verb, index.row()); - } else { - qCWarning(lcActivity) << "Failed: " << objectType << "action" << actionLink._label << "for" << qvariant_cast(index.data(ActivityItemDelegate::ActionTextRole)); - } - } -} - -void ActivityWidget::slotSecondaryButtonClickedOnListView(const QModelIndex &index){ - QList customList = index.data(ActivityItemDelegate::ActionsLinksRole).toList(); - QString objectType = index.data(ActivityItemDelegate::ObjectTypeRole).toString(); - - QList actionLinks; - foreach(QVariant customItem, customList){ - actionLinks << qvariant_cast(customItem); - } - - if(objectType == _remote_share && actionLinks.first()._label == _accept) - actionLinks.removeFirst(); - - if(qvariant_cast(index.data(ActivityItemDelegate::ActionRole)) == Activity::Type::NotificationType){ - const QString accountName = index.data(ActivityItemDelegate::AccountRole).toString(); - if(actionLinks.size() == 1){ - if(actionLinks.at(0)._verb == "DELETE"){ - qCWarning(lcActivity) << "Dismissing Notification/Activity" << qvariant_cast(index.data(ActivityItemDelegate::ActionTextRole)); - slotSendNotificationRequest(index.data(ActivityItemDelegate::AccountRole).toString(), actionLinks.at(0)._link, actionLinks.at(0)._verb, index.row()); - } - } else if(actionLinks.size() > 1){ - QMenu menu; - qCWarning(lcActivity) << "Displaying menu for Notification/Activity" << qvariant_cast(index.data(ActivityItemDelegate::ActionTextRole)); - foreach (ActivityLink actionLink, actionLinks) { - QAction *menuAction = new QAction(actionLink._label, &menu); - connect(menuAction, &QAction::triggered, this, [this, index, accountName, actionLink] { - this->slotSendNotificationRequest(accountName, actionLink._link, actionLink._verb, index.row()); - }); - menu.addAction(menuAction); - } - menu.exec(QCursor::pos()); - } - } - - Activity::Type activityType = qvariant_cast(index.data(ActivityItemDelegate::ActionRole)); - if(activityType == Activity::Type::SyncFileItemType || activityType == Activity::Type::SyncResultType) - slotOpenFile(index); -} - -void ActivityWidget::slotNotificationRequestFinished(int statusCode) -{ - int row = sender()->property("activityRow").toInt(); - - // the ocs API returns stat code 100 or 200 inside the xml if it succeeded. - if (statusCode != OCS_SUCCESS_STATUS_CODE && statusCode != OCS_SUCCESS_STATUS_CODE_V2) { - qCWarning(lcActivity) << "Notification Request to Server failed, leave notification visible."; - } else { - // to do use the model to rebuild the list or remove the item - qCWarning(lcActivity) << "Notification Request to Server successed, rebuilding list."; - _model->removeActivityFromActivityList(row); - } -} - -void ActivityWidget::slotRefreshActivities() -{ - _model->slotRefreshActivity(); -} - -void ActivityWidget::slotRefreshNotifications() -{ - // start a server notification handler if no notification requests - // are running - if (_notificationRequestsRunning == 0) { - ServerNotificationHandler *snh = new ServerNotificationHandler(_accountState); - connect(snh, &ServerNotificationHandler::newNotificationList, - this, &ActivityWidget::slotBuildNotificationDisplay); - - snh->slotFetchNotifications(); - } else { - qCWarning(lcActivity) << "Notification request counter not zero."; - } -} - -void ActivityWidget::slotRemoveAccount() -{ - _model->slotRemoveAccount(); -} - -void ActivityWidget::showLabels() -{ - _ui->_bottomLabel->hide(); // hide whatever was there before - QString t(""); - QSetIterator i(_accountsWithoutActivities); - while (i.hasNext()) { - t.append(tr("
Account %1 does not have activities enabled.").arg(i.next())); - } - if(!t.isEmpty()){ - _ui->_bottomLabel->setTextFormat(Qt::RichText); - _ui->_bottomLabel->setText(t); - _ui->_bottomLabel->show(); - } -} - -void ActivityWidget::slotAccountActivityStatus(int statusCode) -{ - if (!(_accountState && _accountState->account())) { - return; - } - if (statusCode == 999) { - _accountsWithoutActivities.insert(_accountState->account()->displayName()); - } else { - _accountsWithoutActivities.remove(_accountState->account()->displayName()); - } - - checkActivityWidgetVisibility(); - showLabels(); -} - -// FIXME: Reused from protocol widget. Move over to utilities. -QString ActivityWidget::timeString(QDateTime dt, QLocale::FormatType format) const -{ - const QLocale loc = QLocale::system(); - QString dtFormat = loc.dateTimeFormat(format); - static const QRegExp re("(HH|H|hh|h):mm(?!:s)"); - dtFormat.replace(re, "\\1:mm:ss"); - return loc.toString(dt, dtFormat); -} - -void ActivityWidget::storeActivityList(QTextStream &ts) -{ - ActivityList activities = _model->activityList(); - - foreach (Activity activity, activities) { - ts << right - // account name - << qSetFieldWidth(activity._accName.length()) - << activity._accName - // separator - << qSetFieldWidth(2) << " - " - - // date and time - << qSetFieldWidth(activity._dateTime.toString().length()) - << activity._dateTime.toString() - // separator - << qSetFieldWidth(2) << " - " - - // fileq - << qSetFieldWidth(activity._file.length()) - << activity._file - // separator - << qSetFieldWidth(2) << " - " - - // subject - << qSetFieldWidth(activity._subject.length()) - << activity._subject - // separator - << qSetFieldWidth(2) << " - " - - // message - << qSetFieldWidth(activity._message.length()) - << activity._message - << endl; - } -} - -void ActivityWidget::checkActivityWidgetVisibility() -{ - int accountCount = AccountManager::instance()->accounts().count(); - bool hasAccountsWithActivity = - _accountsWithoutActivities.count() != accountCount; - - _ui->_activityList->setVisible(hasAccountsWithActivity); - - emit hideActivityTab(!hasAccountsWithActivity); -} - -void ActivityWidget::slotOpenFile(QModelIndex indx) -{ - qCDebug(lcActivity) << indx.isValid() << indx.data(ActivityItemDelegate::PathRole).toString() << QFile::exists(indx.data(ActivityItemDelegate::PathRole).toString()); - if (indx.isValid()) { - QString fullPath = indx.data(ActivityItemDelegate::PathRole).toString(); - if(!fullPath.isEmpty()){ - if (QFile::exists(fullPath)) { - showInFileManager(fullPath); - } - } - } -} - -// GUI: Display the notifications. -// All notifications in list are coming from the same account -// but in the _widgetForNotifId hash widgets for all accounts are -// collected. -void ActivityWidget::slotBuildNotificationDisplay(const ActivityList &list) -{ - // Whether a new notification was added to the list - bool newNotificationShown = false; - - _model->clearNotifications(); - - foreach (auto activity, list) { - if (_blacklistedNotifications.contains(activity)) { - qCInfo(lcActivity) << "Activity in blacklist, skip"; - continue; - } - - // handle gui logs. In order to NOT annoy the user with every fetching of the - // notifications the notification id is stored in a Set. Only if an id - // is not in the set, it qualifies for guiLog. - // Important: The _guiLoggedNotifications set must be wiped regularly which - // will repeat the gui log. - - // after one hour, clear the gui log notification store - if (_guiLogTimer.elapsed() > 60 * 60 * 1000) { - _guiLoggedNotifications.clear(); - } - - if (!_guiLoggedNotifications.contains(activity._id)) { - newNotificationShown = true; - _guiLoggedNotifications.insert(activity._id); - - // Assemble a tray notification for the NEW notification - ConfigFile cfg; - if(cfg.optionalServerNotifications()){ - if(AccountManager::instance()->accounts().count() == 1){ - emit guiLog(activity._subject, ""); - } else { - emit guiLog(activity._subject, activity._accName); - } - } - } - - _model->addNotificationToActivityList(activity); - } - - // restart the gui log timer now that we show a new notification - if(newNotificationShown) { - _guiLogTimer.start(); - } -} - -void ActivityWidget::slotSendNotificationRequest(const QString &accountName, const QString &link, const QByteArray &verb, int row) -{ - qCInfo(lcActivity) << "Server Notification Request " << verb << link << "on account" << accountName; - - const QStringList validVerbs = QStringList() << "GET" - << "PUT" - << "POST" - << "DELETE"; - - if (validVerbs.contains(verb)) { - AccountStatePtr acc = AccountManager::instance()->account(accountName); - if (acc) { - NotificationConfirmJob *job = new NotificationConfirmJob(acc->account()); - QUrl l(link); - job->setLinkAndVerb(l, verb); - job->setProperty("activityRow", QVariant::fromValue(row)); - connect(job, &AbstractNetworkJob::networkError, - this, &ActivityWidget::slotNotifyNetworkError); - connect(job, &NotificationConfirmJob::jobFinished, - this, &ActivityWidget::slotNotifyServerFinished); - job->start(); - - // count the number of running notification requests. If this member var - // is larger than zero, no new fetching of notifications is started - _notificationRequestsRunning++; - } - } else { - qCWarning(lcActivity) << "Notification Links: Invalid verb:" << verb; - } -} - -void ActivityWidget::endNotificationRequest(int replyCode) -{ - _notificationRequestsRunning--; - slotNotificationRequestFinished(replyCode); -} - -void ActivityWidget::slotNotifyNetworkError(QNetworkReply *reply) -{ - NotificationConfirmJob *job = qobject_cast(sender()); - if (!job) { - return; - } - - int resultCode = reply->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt(); - - endNotificationRequest(resultCode); - qCWarning(lcActivity) << "Server notify job failed with code " << resultCode; -} - -void ActivityWidget::slotNotifyServerFinished(const QString &reply, int replyCode) -{ - NotificationConfirmJob *job = qobject_cast(sender()); - if (!job) { - return; - } - - endNotificationRequest(replyCode); - qCInfo(lcActivity) << "Server Notification reply code" << replyCode << reply; -} - -void ActivityWidget::slotStyleChanged() -{ - // Notify the other widgets (Dark-/Light-Mode switching) - emit styleChanged(); -} - -/* ==================================================================== */ - -ActivitySettings::ActivitySettings(AccountState *accountState, QWidget *parent) - : QWidget(parent) - , _accountState(accountState) -{ - _vbox = new QVBoxLayout(this); - setLayout(_vbox); - - _activityWidget = new ActivityWidget(_accountState, this); - - _vbox->insertWidget(1, _activityWidget); - connect(_activityWidget, &ActivityWidget::guiLog, this, &ActivitySettings::guiLog); - connect(&_notificationCheckTimer, &QTimer::timeout, - this, &ActivitySettings::slotRegularNotificationCheck); - - // Add a progress indicator to spin if the acitivity list is updated. - _progressIndicator = new QProgressIndicator(this); - - // connect a model signal to stop the animation - connect(_activityWidget, &ActivityWidget::rowsInserted, _progressIndicator, &QProgressIndicator::stopAnimation); - connect(_activityWidget, &ActivityWidget::rowsInserted, this, &ActivitySettings::slotDisplayActivities); - - // Connect styleChanged events to our widgets, so they can adapt (Dark-/Light-Mode switching) - connect(this, &ActivitySettings::styleChanged, _activityWidget, &ActivityWidget::slotStyleChanged); -} - -void ActivitySettings::slotDisplayActivities(){ - _vbox->removeWidget(_progressIndicator); -} - -void ActivitySettings::setNotificationRefreshInterval(std::chrono::milliseconds interval) -{ - qCDebug(lcActivity) << "Starting Notification refresh timer with " << interval.count() / 1000 << " sec interval"; - _notificationCheckTimer.start(interval.count()); -} - -void ActivitySettings::slotRemoveAccount() -{ - _activityWidget->slotRemoveAccount(); -} - -void ActivitySettings::slotRefresh() -{ - // QElapsedTimer isn't actually constructed as invalid. - if (!_timeSinceLastCheck.contains(_accountState)) { - _timeSinceLastCheck[_accountState].invalidate(); - } - QElapsedTimer &timer = _timeSinceLastCheck[_accountState]; - - // Fetch Activities only if visible and if last check is longer than 15 secs ago - if (timer.isValid() && timer.elapsed() < NOTIFICATION_REQUEST_FREE_PERIOD) { - qCDebug(lcActivity) << "Do not check as last check is only secs ago: " << timer.elapsed() / 1000; - return; - } - if (_accountState && _accountState->isConnected()) { - if (isVisible() || !timer.isValid()) { - _vbox->insertWidget(0, _progressIndicator); - _vbox->setAlignment(_progressIndicator, Qt::AlignHCenter); - _progressIndicator->startAnimation(); - _activityWidget->slotRefreshActivities(); - } - _activityWidget->slotRefreshNotifications(); - timer.start(); - } -} - -void ActivitySettings::slotRegularNotificationCheck() -{ - slotRefresh(); -} - -bool ActivitySettings::event(QEvent *e) -{ - if (e->type() == QEvent::Show) { - slotRefresh(); - } - return QWidget::event(e); -} - -ActivitySettings::~ActivitySettings() -{ -} - -void ActivitySettings::slotStyleChanged() -{ - if(_progressIndicator) - _progressIndicator->setColor(QGuiApplication::palette().color(QPalette::Text)); - - // Notify the other widgets (Dark-/Light-Mode switching) - emit styleChanged(); -} - -} diff --git a/src/gui/activitywidget.h b/src/gui/activitywidget.h deleted file mode 100644 index ee271c62a..000000000 --- a/src/gui/activitywidget.h +++ /dev/null @@ -1,165 +0,0 @@ -/* - * 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; either version 2 of the License, or - * (at your option) any later version. - * - * 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 ACTIVITYWIDGET_H -#define ACTIVITYWIDGET_H - -#include -#include -#include -#include -#include - -#include "progressdispatcher.h" -#include "owncloudgui.h" -#include "account.h" -#include "activitydata.h" -#include "accountmanager.h" - -#include "ui_activitywidget.h" - -class QPushButton; -class QProgressIndicator; - -namespace OCC { - -class Account; -class AccountStatusPtr; -class JsonApiJob; -class ActivityListModel; - -namespace Ui { - class ActivityWidget; -} -class Application; - -/** - * @brief The ActivityWidget class - * @ingroup gui - * - * The list widget to display the activities, contained in the - * subsequent ActivitySettings widget. - */ - -class ActivityWidget : public QWidget -{ - Q_OBJECT -public: - explicit ActivityWidget(AccountState *accountState, QWidget *parent = nullptr); - ~ActivityWidget(); - QSize sizeHint() const override { return ownCloudGui::settingsDialogSize(); } - void storeActivityList(QTextStream &ts); - - /** - * Adjusts the activity tab's and some widgets' visibility - * - * Based on whether activities are enabled and whether notifications are - * available. - */ - void checkActivityWidgetVisibility(); - -public slots: - void slotOpenFile(QModelIndex indx); - void slotRefreshActivities(); - void slotRefreshNotifications(); - void slotRemoveAccount(); - void slotAccountActivityStatus(int statusCode); - void addError(const QString &folderAlias, const QString &message, ErrorCategory category); - void slotProgressInfo(const QString &folder, const ProgressInfo &progress); - void slotItemCompleted(const QString &folder, const SyncFileItemPtr &item); - void slotStyleChanged(); - -signals: - void guiLog(const QString &, const QString &); - void rowsInserted(); - void hideActivityTab(bool); - void sendNotificationRequest(const QString &accountName, const QString &link, const QByteArray &verb, int row); - void styleChanged(); - -private slots: - void slotBuildNotificationDisplay(const ActivityList &list); - void slotSendNotificationRequest(const QString &accountName, const QString &link, const QByteArray &verb, int row); - void slotNotifyNetworkError(QNetworkReply *); - void slotNotifyServerFinished(const QString &reply, int replyCode); - void endNotificationRequest(int replyCode); - void slotNotificationRequestFinished(int statusCode); - void slotPrimaryButtonClickedOnListView(const QModelIndex &index); - void slotSecondaryButtonClickedOnListView(const QModelIndex &index); - -private: - void customizeStyle(); - void showLabels(); - QString timeString(QDateTime dt, QLocale::FormatType format) const; - Ui::ActivityWidget *_ui; - QSet _accountsWithoutActivities; - QElapsedTimer _guiLogTimer; - QSet _guiLoggedNotifications; - ActivityList _blacklistedNotifications; - - QTimer _removeTimer; - - // number of currently running notification requests. If non zero, - // no query for notifications is started. - int _notificationRequestsRunning; - - ActivityListModel *_model; - AccountState *_accountState; - const QString _accept; - const QString _remote_share; -}; - - -/** - * @brief The ActivitySettings class - * @ingroup gui - * - * Implements a tab for the settings dialog, displaying the three activity - * lists. - */ -class ActivitySettings : public QWidget -{ - Q_OBJECT -public: - explicit ActivitySettings(AccountState *accountState, QWidget *parent = nullptr); - - ~ActivitySettings(); - QSize sizeHint() const override { return ownCloudGui::settingsDialogSize(); } - -public slots: - void slotRefresh(); - void slotRemoveAccount(); - void setNotificationRefreshInterval(std::chrono::milliseconds interval); - void slotStyleChanged(); - -private slots: - void slotRegularNotificationCheck(); - void slotDisplayActivities(); - -signals: - void guiLog(const QString &, const QString &); - void styleChanged(); - -private: - bool event(QEvent *e) override; - - ActivityWidget *_activityWidget; - QProgressIndicator *_progressIndicator; - QVBoxLayout *_vbox; - QTimer _notificationCheckTimer; - QHash _timeSinceLastCheck; - - AccountState *_accountState; -}; -} -#endif // ActivityWIDGET_H diff --git a/src/gui/activitywidget.ui b/src/gui/activitywidget.ui deleted file mode 100644 index 7e5e9f73c..000000000 --- a/src/gui/activitywidget.ui +++ /dev/null @@ -1,98 +0,0 @@ - - - OCC::ActivityWidget - - - - 0 - 0 - 652 - 556 - - - - Form - - - - QLayout::SetDefaultConstraint - - - 0 - - - 0 - - - 0 - - - 0 - - - - - - 0 - 0 - - - - QFrame::StyledPanel - - - QFrame::Sunken - - - 1 - - - QAbstractScrollArea::AdjustToContents - - - QAbstractItemView::NoEditTriggers - - - false - - - Qt::IgnoreAction - - - QListView::Adjust - - - QListView::ListMode - - - 0 - - - true - - - - - - - - 0 - 0 - - - - TextLabel - - - Qt::RichText - - - - - - - _activityList - - - - diff --git a/src/gui/generalsettings.ui b/src/gui/generalsettings.ui index fcdfc073c..1c7c25015 100644 --- a/src/gui/generalsettings.ui +++ b/src/gui/generalsettings.ui @@ -6,7 +6,7 @@ 0 0 - 785 + 516 523 diff --git a/src/gui/networksettings.ui b/src/gui/networksettings.ui index 5d7dc94e8..b9391cf7e 100644 --- a/src/gui/networksettings.ui +++ b/src/gui/networksettings.ui @@ -6,7 +6,7 @@ 0 0 - 563 + 516 444 diff --git a/src/gui/owncloudgui.cpp b/src/gui/owncloudgui.cpp index 27662f715..8916c2846 100644 --- a/src/gui/owncloudgui.cpp +++ b/src/gui/owncloudgui.cpp @@ -207,10 +207,6 @@ void ownCloudGui::slotSyncStateChange(Folder *folder) || result.status() == SyncResult::Error) { Logger::instance()->enterNextLogFile(); } - - if (result.status() == SyncResult::NotYetStarted) { - _settingsDialog->slotRefreshActivity(folder->accountState()); - } } void ownCloudGui::slotFoldersChanged() @@ -792,10 +788,7 @@ void ownCloudGui::setupActions() _navLinksMenu->setEnabled(false); _actionSettings = new QAction(tr("Settings …"), this); _actionNewAccountWizard = new QAction(tr("New account …"), this); - _actionRecent = new QAction(tr("View more activity …"), this); - _actionRecent->setEnabled(true); - QObject::connect(_actionRecent, &QAction::triggered, this, &ownCloudGui::slotShowSyncProtocol); QObject::connect(_actionSettings, &QAction::triggered, this, &ownCloudGui::slotShowSettings); QObject::connect(_actionNewAccountWizard, &QAction::triggered, this, &ownCloudGui::slotNewAccountWizard); _actionHelp = new QAction(tr("Help"), this); @@ -909,8 +902,6 @@ void ownCloudGui::slotRebuildRecentMenus() } else { _recentActionsMenu->addAction(tr("No items synced recently"))->setEnabled(false); } - // add a more... entry. - _recentActionsMenu->addAction(_actionRecent); } /// Returns true if the completion of a given item should show up in the @@ -970,8 +961,6 @@ void ownCloudGui::slotUpdateProgress(const QString &folder, const ProgressInfo & _actionStatus->setText(msg); } - _actionRecent->setIcon(QIcon()); // Fixme: Set a "in-progress"-item eventually. - if (!progress._lastCompletedItem.isEmpty() && shouldShowInRecentsMenu(progress._lastCompletedItem)) { if (Progress::isWarningKind(progress._lastCompletedItem._status)) { diff --git a/src/gui/servernotificationhandler.cpp b/src/gui/servernotificationhandler.cpp deleted file mode 100644 index bcf386033..000000000 --- a/src/gui/servernotificationhandler.cpp +++ /dev/null @@ -1,165 +0,0 @@ -/* - * 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; either version 2 of the License, or - * (at your option) any later version. - * - * 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 "servernotificationhandler.h" -#include "accountstate.h" -#include "capabilities.h" -#include "networkjobs.h" - -#include "iconjob.h" - -#include -#include - -namespace OCC { - -Q_LOGGING_CATEGORY(lcServerNotification, "nextcloud.gui.servernotification", QtInfoMsg) - -const QString notificationsPath = QLatin1String("ocs/v2.php/apps/notifications/api/v2/notifications"); -const char propertyAccountStateC[] = "oc_account_state"; -const int successStatusCode = 200; -const int notModifiedStatusCode = 304; -QMap ServerNotificationHandler::iconCache; - -ServerNotificationHandler::ServerNotificationHandler(AccountState *accountState, QObject *parent) - : QObject(parent) - , _accountState(accountState) -{ -} - -void ServerNotificationHandler::slotFetchNotifications() -{ - // check connectivity and credentials - if (!(_accountState && _accountState->isConnected() && - _accountState->account() && _accountState->account()->credentials() && - _accountState->account()->credentials()->ready())) { - deleteLater(); - return; - } - // check if the account has notifications enabled. If the capabilities are - // not yet valid, its assumed that notifications are available. - if (_accountState->account()->capabilities().isValid()) { - if (!_accountState->account()->capabilities().notificationsAvailable()) { - qCInfo(lcServerNotification) << "Account" << _accountState->account()->displayName() << "does not have notifications enabled."; - deleteLater(); - return; - } - } - - // if the previous notification job has finished, start next. - _notificationJob = new JsonApiJob(_accountState->account(), notificationsPath, this); - QObject::connect(_notificationJob.data(), &JsonApiJob::jsonReceived, - this, &ServerNotificationHandler::slotNotificationsReceived); - QObject::connect(_notificationJob.data(), &JsonApiJob::etagResponseHeaderReceived, - this, &ServerNotificationHandler::slotEtagResponseHeaderReceived); - _notificationJob->setProperty(propertyAccountStateC, QVariant::fromValue(_accountState)); - _notificationJob->addRawHeader("If-None-Match", _accountState->notificationsEtagResponseHeader()); - _notificationJob->start(); -} - -void ServerNotificationHandler::slotEtagResponseHeaderReceived(const QByteArray &value, int statusCode){ - if(statusCode == successStatusCode){ - qCWarning(lcServerNotification) << "New Notification ETag Response Header received " << value; - AccountState *account = qvariant_cast(sender()->property(propertyAccountStateC)); - account->setNotificationsEtagResponseHeader(value); - } -} - -void ServerNotificationHandler::slotIconDownloaded(QByteArray iconData){ - QPixmap pixmap; - pixmap.loadFromData(iconData); - iconCache.insert(sender()->property("activityId").toInt(), QIcon(pixmap)); -} - -void ServerNotificationHandler::slotNotificationsReceived(const QJsonDocument &json, int statusCode) -{ - if (statusCode != successStatusCode && statusCode != notModifiedStatusCode) { - qCWarning(lcServerNotification) << "Notifications failed with status code " << statusCode; - deleteLater(); - return; - } - - if (statusCode == notModifiedStatusCode) { - qCWarning(lcServerNotification) << "Status code " << statusCode << " Not Modified - No new notifications."; - deleteLater(); - return; - } - - auto notifies = json.object().value("ocs").toObject().value("data").toArray(); - - AccountState *ai = qvariant_cast(sender()->property(propertyAccountStateC)); - - ActivityList list; - - foreach (auto element, notifies) { - Activity a; - auto json = element.toObject(); - a._type = Activity::NotificationType; - a._accName = ai->account()->displayName(); - a._id = json.value("notification_id").toInt(); - - //need to know, specially for remote_share - a._objectType = json.value("object_type").toString(); - a._status = 0; - - a._subject = json.value("subject").toString(); - a._message = json.value("message").toString(); - - if(!json.value("icon").toString().isEmpty()){ - IconJob *iconJob = new IconJob(QUrl(json.value("icon").toString())); - iconJob->setProperty("activityId", a._id); - connect(iconJob, &IconJob::jobFinished, this, &ServerNotificationHandler::slotIconDownloaded); - } - - QUrl link(json.value("link").toString()); - if (!link.isEmpty()) { - if(link.host().isEmpty()){ - link.setScheme(ai->account()->url().scheme()); - link.setHost(ai->account()->url().host()); - } - if (link.port() == -1) { - link.setPort(ai->account()->url().port()); - } - } - a._link = link; - a._dateTime = QDateTime::fromString(json.value("datetime").toString(), Qt::ISODate); - - auto actions = json.value("actions").toArray(); - foreach (auto action, actions) { - auto actionJson = action.toObject(); - ActivityLink al; - al._label = QUrl::fromPercentEncoding(actionJson.value("label").toString().toUtf8()); - al._link = actionJson.value("link").toString(); - al._verb = actionJson.value("type").toString().toUtf8(); - al._isPrimary = actionJson.value("primary").toBool(); - - a._links.append(al); - } - - // Add another action to dismiss notification on server - // https://github.com/owncloud/notifications/blob/master/docs/ocs-endpoint-v1.md#deleting-a-notification-for-a-user - ActivityLink al; - al._label = tr("Dismiss"); - al._link = Utility::concatUrlPath(ai->account()->url(), notificationsPath + "/" + QString::number(a._id)).toString(); - al._verb = "DELETE"; - al._isPrimary = false; - a._links.append(al); - - list.append(a); - } - emit newNotificationList(list); - - deleteLater(); -} -} diff --git a/src/gui/servernotificationhandler.h b/src/gui/servernotificationhandler.h deleted file mode 100644 index f0276da52..000000000 --- a/src/gui/servernotificationhandler.h +++ /dev/null @@ -1,50 +0,0 @@ -/* - * 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; either version 2 of the License, or - * (at your option) any later version. - * - * 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 SERVERNOTIFICATIONHANDLER_H -#define SERVERNOTIFICATIONHANDLER_H - -#include - -#include "activitywidget.h" - -class QJsonDocument; - -namespace OCC { - -class ServerNotificationHandler : public QObject -{ - Q_OBJECT -public: - explicit ServerNotificationHandler(AccountState *accountState, QObject *parent = nullptr); - static QMap iconCache; - -signals: - void newNotificationList(ActivityList); - -public slots: - void slotFetchNotifications(); - -private slots: - void slotNotificationsReceived(const QJsonDocument &json, int statusCode); - void slotEtagResponseHeaderReceived(const QByteArray &value, int statusCode); - void slotIconDownloaded(QByteArray iconData); - -private: - QPointer _notificationJob; - AccountState *_accountState; -}; -} - -#endif // SERVERNOTIFICATIONHANDLER_H diff --git a/src/gui/settingsdialog.cpp b/src/gui/settingsdialog.cpp index a80de4da5..6fa7bfa47 100644 --- a/src/gui/settingsdialog.cpp +++ b/src/gui/settingsdialog.cpp @@ -23,7 +23,6 @@ #include "configfile.h" #include "progressdispatcher.h" #include "owncloudgui.h" -#include "activitywidget.h" #include "accountmanager.h" #include @@ -189,39 +188,13 @@ void SettingsDialog::showFirstPage() } } -void SettingsDialog::showActivityPage() -{ - if (auto account = qvariant_cast(sender()->property("account"))) { - _activitySettings[account]->show(); - _ui->stack->setCurrentWidget(_activitySettings[account]); - } -} - void SettingsDialog::showIssuesList(AccountState *account) { - for (auto it = _actionGroupWidgets.begin(); it != _actionGroupWidgets.end(); ++it) { + /*for (auto it = _actionGroupWidgets.begin(); it != _actionGroupWidgets.end(); ++it) { if (it.value() == _activitySettings[account]) { it.key()->activate(QAction::ActionEvent::Trigger); break; } - } -} - -void SettingsDialog::activityAdded(AccountState *s){ - _ui->stack->addWidget(_activitySettings[s]); - connect(_activitySettings[s], &ActivitySettings::guiLog, _gui, - &ownCloudGui::slotShowOptionalTrayMessage); - - ConfigFile cfg; - _activitySettings[s]->setNotificationRefreshInterval(cfg.notificationRefreshInterval()); - - // Note: all the actions have a '\n' because the account name is in two lines and - // all buttons must have the same size in order to keep a good layout - QAction *action = createColorAwareAction(QLatin1String(":/client/resources/activity.png"), tr("Activity")); - action->setProperty("account", QVariant::fromValue(s)); - _toolBar->insertAction(_actionBefore, action); - _actionGroup->addAction(action); - _actionGroupWidgets.insert(action, _activitySettings[s]); - connect(action, &QAction::triggered, this, &SettingsDialog::showActivityPage); + }*/ } void SettingsDialog::accountAdded(AccountState *s) @@ -229,16 +202,6 @@ void SettingsDialog::accountAdded(AccountState *s) auto height = _toolBar->sizeHint().height(); bool brandingSingleAccount = !Theme::instance()->multiAccount(); - _activitySettings[s] = new ActivitySettings(s, this); - - - - // if this is not the first account, then before we continue to add more accounts we add a separator - if(AccountManager::instance()->accounts().first().data() != s && - AccountManager::instance()->accounts().size() >= 1){ - _actionGroupWidgets.insert(_toolBar->insertSeparator(_actionBefore), _activitySettings[s]); - } - QAction *accountAction; QImage avatar = s->account()->avatar(); const QString actionText = brandingSingleAccount ? tr("Account") : s->account()->displayName(); @@ -270,15 +233,8 @@ void SettingsDialog::accountAdded(AccountState *s) connect(s->account().data(), &Account::accountChangedAvatar, this, &SettingsDialog::slotAccountAvatarChanged); connect(s->account().data(), &Account::accountChangedDisplayName, this, &SettingsDialog::slotAccountDisplayNameChanged); - // Refresh immediatly when getting online - connect(s, &AccountState::isConnectedChanged, this, &SettingsDialog::slotRefreshActivityAccountStateSender); - // Connect styleChanged event, to adapt (Dark-/Light-Mode switching) connect(this, &SettingsDialog::styleChanged, accountSettings, &AccountSettings::slotStyleChanged); - connect(this, &SettingsDialog::styleChanged, _activitySettings[s], &ActivitySettings::slotStyleChanged); - - activityAdded(s); - slotRefreshActivity(s); } void SettingsDialog::slotAccountAvatarChanged() @@ -334,19 +290,6 @@ void SettingsDialog::accountRemoved(AccountState *s) _actionForAccount.remove(s->account().data()); } - if(_activitySettings.contains(s)){ - _activitySettings[s]->slotRemoveAccount(); - _activitySettings[s]->hide(); - - // get the settings widget and the separator - foreach(QAction *action, _actionGroupWidgets.keys(_activitySettings[s])){ - _actionGroupWidgets.remove(action); - _toolBar->removeAction(action); - } - _toolBar->widgetForAction(_actionBefore)->hide(); - _activitySettings.remove(s); - } - // Hide when the last account is deleted. We want to enter the same // state we'd be in the client was started up without an account // configured. @@ -416,15 +359,4 @@ QAction *SettingsDialog::createColorAwareAction(const QString &iconPath, const Q return createActionWithIcon(coloredIcon, text, iconPath); } -void SettingsDialog::slotRefreshActivityAccountStateSender() -{ - slotRefreshActivity(qobject_cast(sender())); -} - -void SettingsDialog::slotRefreshActivity(AccountState *accountState) -{ - if (accountState->isConnected()) - _activitySettings[accountState]->slotRefresh(); -} - } // namespace OCC diff --git a/src/gui/settingsdialog.h b/src/gui/settingsdialog.h index f5ec654d9..8242522c2 100644 --- a/src/gui/settingsdialog.h +++ b/src/gui/settingsdialog.h @@ -37,7 +37,6 @@ class AccountSettings; class Application; class FolderMan; class ownCloudGui; -class ActivitySettings; /** * @brief The SettingsDialog class @@ -55,11 +54,8 @@ public: public slots: void showFirstPage(); - void showActivityPage(); void showIssuesList(AccountState *account); void slotSwitchPage(QAction *action); - void slotRefreshActivity(AccountState *accountState); - void slotRefreshActivityAccountStateSender(); void slotAccountAvatarChanged(); void slotAccountDisplayNameChanged(); @@ -78,7 +74,6 @@ private slots: private: void customizeStyle(); - void activityAdded(AccountState *); QAction *createColorAwareAction(const QString &iconName, const QString &fileName); QAction *createActionWithIcon(const QIcon &icon, const QString &text, const QString &iconPath = QString()); @@ -95,7 +90,6 @@ private: QHash _actionForAccount; QToolBar *_toolBar; - QMap _activitySettings; ownCloudGui *_gui; }; diff --git a/src/gui/settingsdialog.ui b/src/gui/settingsdialog.ui index 3f886ed22..18632134f 100644 --- a/src/gui/settingsdialog.ui +++ b/src/gui/settingsdialog.ui @@ -6,7 +6,7 @@ 0 0 - 693 + 516 457 diff --git a/src/gui/systray.cpp b/src/gui/systray.cpp index 97d6a1a03..c377c69bb 100644 --- a/src/gui/systray.cpp +++ b/src/gui/systray.cpp @@ -42,14 +42,12 @@ Systray::Systray() // TODO: make singleton, provide ::instance() { // Create QML tray engine, build component, set C++ backend context used in window.qml // Use pointer instead of engine() helper function until Qt 5.12 is minimum standard - QQmlEngine *engine = new QQmlEngine; - _trayComponent = new QQmlComponent(engine, QUrl(QStringLiteral("qrc:/qml/src/gui/tray/window.qml"))); - _trayContext = engine->contextForObject(_trayComponent->create()); - - engine->addImageProvider("avatars", new ImageProvider); - engine->rootContext()->setContextProperty("userModelBackend", UserModel::instance()); - engine->rootContext()->setContextProperty("activityModel", ActivityModel::instance()); - engine->rootContext()->setContextProperty("systrayBackend", this); + _trayEngine = new QQmlEngine; + _trayEngine->addImageProvider("avatars", new ImageProvider); + _trayEngine->rootContext()->setContextProperty("userModelBackend", UserModel::instance()); + _trayEngine->rootContext()->setContextProperty("systrayBackend", this); + _trayComponent = new QQmlComponent(_trayEngine, QUrl(QStringLiteral("qrc:/qml/src/gui/tray/window.qml"))); + _trayContext = _trayEngine->contextForObject(_trayComponent->create()); // TODO: hack to pass the icon to QML //ctxt->setContextProperty("theme", QLatin1String("colored")); @@ -59,8 +57,6 @@ Systray::Systray() // TODO: make singleton, provide ::instance() slotChangeActivityModel(AccountManager::instance()->accounts().first()); } - //connect(AccountManager::instance(), &AccountManager::accountAdded, - // this, &Systray::slotChangeActivityModel); hideWindow(); } @@ -72,6 +68,7 @@ void Systray::slotChangeActivityModel(const AccountStatePtr account) { _currentAccount = account; emit currentUserChanged(); + _trayEngine->rootContext()->setContextProperty("activityModel", UserModel::instance()->currentActivityModel()); } void Systray::showMessage(const QString &title, const QString &message, MessageIcon icon, int millisecondsTimeoutHint) diff --git a/src/gui/systray.h b/src/gui/systray.h index f99f024e6..50c277186 100644 --- a/src/gui/systray.h +++ b/src/gui/systray.h @@ -62,6 +62,7 @@ private slots: private: AccountStatePtr _currentAccount; + QQmlEngine *_trayEngine; QQmlComponent *_trayComponent; QQmlContext *_trayContext; }; diff --git a/src/gui/activitydata.cpp b/src/gui/tray/ActivityData.cpp similarity index 100% rename from src/gui/activitydata.cpp rename to src/gui/tray/ActivityData.cpp diff --git a/src/gui/activitydata.h b/src/gui/tray/ActivityData.h similarity index 100% rename from src/gui/activitydata.h rename to src/gui/tray/ActivityData.h diff --git a/src/gui/activitylistmodel.cpp b/src/gui/tray/ActivityListModel.cpp similarity index 93% rename from src/gui/activitylistmodel.cpp rename to src/gui/tray/ActivityListModel.cpp index eeb0cc836..4a875d2f6 100644 --- a/src/gui/activitylistmodel.cpp +++ b/src/gui/tray/ActivityListModel.cpp @@ -24,21 +24,18 @@ #include "accountmanager.h" #include "folderman.h" #include "accessmanager.h" -#include "activityitemdelegate.h" -#include "activitydata.h" -#include "activitylistmodel.h" +#include "ActivityData.h" +#include "ActivityListModel.h" #include "theme.h" -#include "servernotificationhandler.h" - namespace OCC { Q_LOGGING_CATEGORY(lcActivity, "nextcloud.gui.activity", QtInfoMsg) -ActivityListModel::ActivityListModel(AccountState *accountState, QWidget *parent) - : QAbstractListModel(parent) +ActivityListModel::ActivityListModel(AccountState *accountState, QObject* parent) + : QAbstractListModel() , _accountState(accountState) { } @@ -61,7 +58,7 @@ QVariant ActivityListModel::data(const QModelIndex &index, int role) const QStringList list; switch (role) { - case ActivityItemDelegate::PathRole: + case PathRole: if(!a._file.isEmpty()){ auto folder = FolderMan::instance()->folder(a._folder); QString relPath(a._file); @@ -79,7 +76,7 @@ QVariant ActivityListModel::data(const QModelIndex &index, int role) const } } return QVariant(); - case ActivityItemDelegate::ActionsLinksRole:{ + case ActionsLinksRole:{ QList customList; foreach (ActivityLink customItem, a._links) { QVariant customVariant; @@ -88,16 +85,16 @@ QVariant ActivityListModel::data(const QModelIndex &index, int role) const } return customList; } - case ActivityItemDelegate::ActionIconRole:{ + case ActionIconRole:{ ActionIcon actionIcon; if(a._type == Activity::NotificationType){ - QIcon cachedIcon = ServerNotificationHandler::iconCache.value(a._id); + /*QIcon cachedIcon = ServerNotificationHandler::iconCache.value(a._id); if(!cachedIcon.isNull()) { actionIcon.iconType = ActivityIconType::iconUseCached; actionIcon.cachedIcon = cachedIcon; } else { actionIcon.iconType = ActivityIconType::iconBell; - } + }*/ } else if(a._type == Activity::SyncResultType){ actionIcon.iconType = ActivityIconType::iconStateError; } else if(a._type == Activity::SyncFileItemType){ @@ -123,24 +120,24 @@ QVariant ActivityListModel::data(const QModelIndex &index, int role) const icn.setValue(actionIcon); return icn; } - case ActivityItemDelegate::ObjectTypeRole: + case ObjectTypeRole: return a._objectType; - case ActivityItemDelegate::ActionRole:{ + case ActionRole:{ QVariant type; type.setValue(a._type); return type; } - case ActivityItemDelegate::ActionTextRole: + case ActionTextRole: return a._subject; - case ActivityItemDelegate::MessageRole: + case MessageRole: return a._message; - case ActivityItemDelegate::LinkRole: + case LinkRole: return a._link; - case ActivityItemDelegate::AccountRole: + case AccountRole: return a._accName; - case ActivityItemDelegate::PointInTimeRole: + case PointInTimeRole: return QString("%1 (%2)").arg(a._dateTime.toLocalTime().toString(Qt::DefaultLocaleShortDate), Utility::timeAgoInWords(a._dateTime.toLocalTime())); - case ActivityItemDelegate::AccountConnectedRole: + case AccountConnectedRole: return (ast && ast->isConnected()); default: return QVariant(); diff --git a/src/gui/activitylistmodel.h b/src/gui/tray/ActivityListModel.h similarity index 88% rename from src/gui/activitylistmodel.h rename to src/gui/tray/ActivityListModel.h index 1ff352beb..135301592 100644 --- a/src/gui/activitylistmodel.h +++ b/src/gui/tray/ActivityListModel.h @@ -47,12 +47,27 @@ public: iconStateInfo, iconStateSync }; + + enum datarole { ActionIconRole = Qt::UserRole + 1, + UserIconRole, + AccountRole, + ObjectTypeRole, + ActionsLinksRole, + ActionTextRole, + ActionRole, + MessageRole, + PathRole, + LinkRole, + PointInTimeRole, + AccountConnectedRole, + SyncFileStatusRole }; + struct ActionIcon { ActivityIconType iconType; QIcon cachedIcon; }; - explicit ActivityListModel(AccountState *accountState, QWidget *parent = nullptr); + explicit ActivityListModel(AccountState *accountState, QObject* parent = 0); QVariant data(const QModelIndex &index, int role) const override; int rowCount(const QModelIndex &parent = QModelIndex()) const override; diff --git a/src/gui/tray/UserLine.qml b/src/gui/tray/UserLine.qml index 7dd648fda..8aaa58398 100644 --- a/src/gui/tray/UserLine.qml +++ b/src/gui/tray/UserLine.qml @@ -6,7 +6,7 @@ import QtQuick.Layouts 1.2 MenuItem { Connections { - target: systrayBackend + target: userModelBackend onRefreshUserMenu: { userLine.visible = isCurrentUser ? false : true userLine.height = isCurrentUser ? 0 : 60 @@ -17,7 +17,6 @@ MenuItem { visible: isCurrentUser ? false : true width: 216 height: isCurrentUser ? 0 : 60 - //color: "transparent" Rectangle { id: userLineBackground @@ -43,7 +42,7 @@ MenuItem { } onClicked: { - systrayBackend.switchCurrentUser(index) + userModelBackend.switchCurrentUser(index) } RowLayout { @@ -55,7 +54,7 @@ MenuItem { id: accountAvatar Layout.leftMargin: 4 verticalAlignment: Qt.AlignCenter - source: avatar + source: ("image://avatars/" + index) Layout.preferredHeight: (userLineBackground.height -16) Layout.preferredWidth: (userLineBackground.height -16) } diff --git a/src/gui/tray/UserModel.cpp b/src/gui/tray/UserModel.cpp index 95591226f..9ee8ae42a 100644 --- a/src/gui/tray/UserModel.cpp +++ b/src/gui/tray/UserModel.cpp @@ -10,6 +10,7 @@ namespace OCC { User::User(AccountStatePtr &account, const bool &isCurrent) : _account(account) , _isCurrentUser(isCurrent) + , _activityModel(new ActivityListModel(_account.data())) { } @@ -32,6 +33,11 @@ Folder *User::getFolder() } } +ActivityListModel *User::getActivityModel() +{ + return _activityModel; +} + void User::openLocalFolder() { QDesktopServices::openUrl(this->getFolder()->path()); @@ -67,11 +73,17 @@ QImage User::avatar() const { QImage img = AvatarJob::makeCircularAvatar(_account->account()->avatar()); if (img.isNull()) { - img = QImage(":/client/resources/account.svg"); + img = AvatarJob::makeCircularAvatar(QImage(":/client/resources/account.png")); } return img; } +bool User::serverHasTalk() const +{ + auto test = _account->hasTalk(); + return _account->hasTalk(); +} + bool User::isCurrentUser() const { return _isCurrentUser; @@ -79,6 +91,7 @@ bool User::isCurrentUser() const bool User::isConnected() const { + bool test = (_account->connectionStatus() == AccountState::ConnectionStatus::Connected); return (_account->connectionStatus() == AccountState::ConnectionStatus::Connected); } @@ -143,17 +156,26 @@ Q_INVOKABLE QString UserModel::currentUserServer() return _users[_currentUserId].server(); } +Q_INVOKABLE bool UserModel::currentServerHasTalk() +{ + return _users[_currentUserId].serverHasTalk(); +} + void UserModel::addUser(AccountStatePtr &user, const bool &isCurrent) { - auto newUser = User(user, isCurrent); beginInsertRows(QModelIndex(), rowCount(), rowCount()); - _users << newUser; + _users << User(user, isCurrent); if (isCurrent) { - _currentUserId = _users.indexOf(newUser); + _currentUserId = _users.indexOf(_users.last()); } endInsertRows(); } +int UserModel::currentUserIndex() +{ + return _currentUserId; +} + Q_INVOKABLE void UserModel::openCurrentAccountLocalFolder() { _users[_currentUserId].openLocalFolder(); @@ -204,74 +226,9 @@ QHash UserModel::roleNames() const return roles; } -/*-------------------------------------------------------------------------------------*/ - -QString UserActivity::type() const -{ - return "Test"; -} -QString UserActivity::fileName() const -{ - return "Test"; -} - -QString UserActivity::info() const -{ - return "Test"; -} - - -ActivityModel *ActivityModel::_instance = nullptr; - -ActivityModel *ActivityModel::instance() -{ - if (_instance == nullptr) { - _instance = new ActivityModel(); - } - return _instance; -} - -ActivityModel::ActivityModel(QObject *parent) - : QAbstractListModel() -{ -} - -QHash ActivityModel::roleNames() const -{ - QHash roles; - roles[TypeRole] = "type"; - roles[FileNameRole] = "filename"; - roles[InfoRole] = "info"; - return roles; -} - -int ActivityModel::rowCount(const QModelIndex &parent) const +ActivityListModel *UserModel::currentActivityModel() { - Q_UNUSED(parent); - return _activities.count(); -} - -QVariant ActivityModel::data(const QModelIndex &index, int role) const -{ - if (index.row() < 0 || index.row() >= _activities.count()) { - return QVariant(); - } - - const UserActivity &activity = _activities[index.row()]; - if (role == TypeRole) - return activity.type(); - else if (role == FileNameRole) - return activity.fileName(); - else if (role == InfoRole) - return activity.info(); - return QVariant(); -} - -void ActivityModel::addActivity(const UserActivity &activity) -{ - beginInsertRows(QModelIndex(), rowCount(), rowCount()); - _activities << activity; - endInsertRows(); + return _users[currentUserIndex()].getActivityModel(); } /*-------------------------------------------------------------------------------------*/ diff --git a/src/gui/tray/UserModel.h b/src/gui/tray/UserModel.h index 35eb3f9c2..f93d33d8d 100644 --- a/src/gui/tray/UserModel.h +++ b/src/gui/tray/UserModel.h @@ -6,6 +6,7 @@ #include #include +#include "ActivityListModel.h" #include "accountmanager.h" #include "folderman.h" @@ -22,15 +23,18 @@ public: bool isCurrentUser() const; void setCurrentUser(const bool &isCurrent); Folder *getFolder(); + ActivityListModel *getActivityModel(); void openLocalFolder(); QString name() const; QString server() const; + bool serverHasTalk() const; QImage avatar() const; QString id() const; private: AccountStatePtr _account; bool _isCurrentUser; + ActivityListModel *_activityModel; }; class UserModel : public QAbstractListModel @@ -41,6 +45,7 @@ public: virtual ~UserModel() {}; void addUser(AccountStatePtr &user, const bool &isCurrent = false); + int currentUserIndex(); int rowCount(const QModelIndex &parent = QModelIndex()) const; @@ -54,8 +59,11 @@ public: Q_INVOKABLE bool isCurrentUserConnected(); Q_INVOKABLE QString currentUserName(); Q_INVOKABLE QString currentUserServer(); + Q_INVOKABLE bool currentServerHasTalk(); Q_INVOKABLE void switchCurrentUser(const int &id); + ActivityListModel *currentActivityModel(); + enum UserRoles { NameRole = Qt::UserRole + 1, ServerRole, @@ -85,40 +93,6 @@ private: void initUserList(); }; -class UserActivity -{ -public: - QString type() const; - QString fileName() const; - QString info() const; -}; - -class ActivityModel : public QAbstractListModel -{ - Q_OBJECT -public: - static ActivityModel *instance(); - virtual ~ActivityModel() {}; - - int rowCount(const QModelIndex &parent = QModelIndex()) const; - QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const; - void addActivity(const UserActivity &activity); - - enum ActivityRoles { - TypeRole = Qt::UserRole + 1, - FileNameRole, - InfoRole - }; - -protected: - QHash roleNames() const; - -private: - static ActivityModel *_instance; - ActivityModel(QObject *parent = 0); - QList _activities; -}; - class ImageProvider : public QQuickImageProvider { public: diff --git a/src/gui/tray/window.qml b/src/gui/tray/window.qml index 28e61f466..337302960 100644 --- a/src/gui/tray/window.qml +++ b/src/gui/tray/window.qml @@ -20,15 +20,25 @@ Window { } } + onVisibleChanged: { + currentAccountAvatar.source = "" + currentAccountAvatar.source = "image://avatars/currentUser" + currentAccountUser.text = userModelBackend.currentUserName(); + currentAccountServer.text = userModelBackend.currentUserServer(); + trayWindowTalkButton.visible = userModelBackend.currentServerHasTalk() ? true : false; + } + Connections { target: userModelBackend onRefreshCurrentUserGui: { - currentAccountAvatar.source = userModelBackend.currentUserAvatar() - currentAccountUser.text = userModelBackend.currentUserName() - currentAccountServer.text = userModelBackend.currentUserServer() + currentAccountAvatar.source = "" + currentAccountAvatar.source = "image://avatars/currentUser" + currentAccountUser.text = userModelBackend.currentUserName(); + currentAccountServer.text = userModelBackend.currentUserServer(); } onNewUserSelected: { - accountMenu.close() + accountMenu.close(); + trayWindowTalkButton.visible = userModelBackend.currentServerHasTalk() ? true : false; } } @@ -85,6 +95,7 @@ Window { hoverEnabled: true onClicked: { + accMenuLoginButton.text = (userModelBackend.isCurrentUserConnected() ? "Logout" : "Login") accountMenu.open() } @@ -109,7 +120,7 @@ Window { MenuSeparator { id: accountMenuSeparator } MenuItem { - text: (userModelBackend.isCurrentUserConnected() ? "Logout" : "Login") + id: accMenuLoginButton onClicked: (userModelBackend.isCurrentUserConnected() ? userModelBackend.logout() : userModelBackend.login() ) @@ -188,7 +199,8 @@ Window { id: currentAccountAvatar Layout.leftMargin: 8 verticalAlignment: Qt.AlignCenter - source: userModelBackend.currentUserAvatar() + cache: false + source: "image://avatars/currentUser" Layout.preferredHeight: (trayWindowHeaderBackground.height -16) Layout.preferredWidth: (trayWindowHeaderBackground.height -16) } @@ -262,6 +274,7 @@ Window { Layout.preferredWidth: (trayWindowHeaderBackground.height) Layout.preferredHeight: (trayWindowHeaderBackground.height) flat: true + visible: userModelBackend.currentServerHasTalk() ? true : false icon.source: "qrc:///client/theme/white/talk-app.svg" icon.color: "transparent" @@ -289,6 +302,7 @@ Window { Layout.preferredWidth: (trayWindowHeaderBackground.height) Layout.preferredHeight: (trayWindowHeaderBackground.height) flat: true + visible: false icon.source: "qrc:///client/theme/white/more-apps.svg" icon.color: "transparent" diff --git a/src/libsync/capabilities.cpp b/src/libsync/capabilities.cpp index 39df7d167..d0c5f727f 100644 --- a/src/libsync/capabilities.cpp +++ b/src/libsync/capabilities.cpp @@ -103,7 +103,8 @@ bool Capabilities::isValid() const return !_capabilities.isEmpty(); } -bool Capabilities::hasActivities() const { +bool Capabilities::hasActivities() const +{ return _capabilities.contains("activity"); } -- 2.30.2