diff --git a/client.qrc b/client.qrc index 506476d74..69fdd7a0b 100644 --- a/client.qrc +++ b/client.qrc @@ -24,6 +24,8 @@ <file>resources/delete.png</file> <file>resources/close.svg</file> <file>resources/bell.svg</file> + <file>resources/link.svg</file> + <file>resources/files.svg</file> </qresource> <qresource prefix="/"/> </RCC> diff --git a/resources/files.svg b/resources/files.svg new file mode 100644 index 000000000..8ed1bf118 --- /dev/null +++ b/resources/files.svg @@ -0,0 +1 @@ +<svg xmlns="http://www.w3.org/2000/svg" height="16" width="16" version="1"><path d="M1.457 1.997c-.25 0-.46.21-.46.46v11.08c0 .26.202.46.46.46h13.08c.258 0 .46-.2.46-.46V4.46c0-.25-.21-.463-.46-.463h-6.54l-2-2z" /></svg> \ No newline at end of file diff --git a/resources/link.svg b/resources/link.svg new file mode 100644 index 000000000..08127c001 --- /dev/null +++ b/resources/link.svg @@ -0,0 +1 @@ +<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewbox="0 0 16 16"><path d="M7.4 0C3.3 0 0 3.3 0 7.4s3.3 7.4 7.4 7.4 7.4-3.3 7.4-7.4S11.5 0 7.4 0zm.8.9c1.3 0 2.4.8 3.5 1.3l1.8 2.5-.3 1.1.6.3V8.5c-.2.7-.6 1.3-.9 2-.2.1 0-.8-.1-1 0-.6-.5-.6-.9-.2-.4.3-1.4.3-1.5-.4-.3-.8 0-1.7.3-2.5l-.6-.7.2-1.8-.8-.9.2-1-1-.6c-.2-.2-.6-.2-.7-.4.1 0 .2-.1.2-.1zM5.6 1s.1 0 .1.1c.4.2-.1.4-.2.6-.5.3.3.7.5 1 .4-.1.8-.7 1.4-.5.7-.2.6.6 1.1 1 .1.2.9.8.4.6-.5-.4-1-.4-1.3.1-.8.5-.3-.9-.7-1.2-.6-.7-.4.5-.4.9-.4 0-1.1-.3-1.5.2l.4.6.5-.7c0-.3.1.2.3.3.1.2.8.7.3.9-.8.4-1.4 1.1-2.1 1.7-.2.5-.7.4-1 0-.7-.4-.7.7-.6 1.1l.6-.4v1.1c-.4.4-.9-.7-1.3-.9V5.9c0-.4-.1-.9 0-1.3.8-.9 1.7-1.9 2.2-3h.8c.6.2.3-.7.5-.6zM4.4 9.2c.1 0 .2 0 .3.1.8.1 1.4.7 2 1.1.5.5 1.6.3 1.7 1.2-.2.9-1.1 1.4-1.8 1.7-.2.1-.4.2-.6.2-.7.2-1-.6-1.2-1.1-.3-.7-1.1-1.2-1-2.1 0-.4.2-1 .6-1.1z"/></svg> \ No newline at end of file diff --git a/src/gui/activitydata.h b/src/gui/activitydata.h index 5a95b68be..3518cb1cd 100644 --- a/src/gui/activitydata.h +++ b/src/gui/activitydata.h @@ -49,7 +49,8 @@ public: enum Type { ActivityType, - NotificationType + NotificationType, + ErrorType }; Type _type; @@ -60,6 +61,7 @@ public: QUrl _link; QDateTime _dateTime; QString _accName; + int _status; QVector<ActivityLink> _links; /** diff --git a/src/gui/activityitemdelegate.cpp b/src/gui/activityitemdelegate.cpp index 06c4ae142..7530c806f 100644 --- a/src/gui/activityitemdelegate.cpp +++ b/src/gui/activityitemdelegate.cpp @@ -159,6 +159,43 @@ void ActivityItemDelegate::paint(QPainter *painter, const QStyleOptionViewItem & primaryButton.features |= QStyleOptionButton::DefaultButton; primaryButton.state |= QStyle::State_Raised; + // save info to be able to filter mouse clicks + _buttonHeight = buttonSize; + _spaceBetweenButtons = leftMargin; + _primaryButtonWidth = primaryButton.rect.size().width(); + _secondaryButtonWidth = secondaryButton.rect.size().width(); + } else if(activityType == Activity::Type::ErrorType){ + int rightMargin = margin; + int leftMargin = margin * offset; + int top = option.rect.top() + margin - offset; + int buttonSize = option.rect.height()/2.5; + + // Secondary will be 'Dismiss' or '...' + secondaryButton.rect = option.rect; + secondaryButton.icon = QIcon(QLatin1String(":/client/resources/files.svg")); + + int right = option.rect.right() - rightMargin; + int left = right - buttonSize; + secondaryButton.iconSize = QSize(buttonSize, buttonSize); + secondaryButton.rect.setLeft(left); + secondaryButton.rect.setRight(right); + secondaryButton.rect.setTop(top); + secondaryButton.rect.setHeight(_buttonHeight); + secondaryButton.features |= QStyleOptionButton::DefaultButton; + secondaryButton.state |= QStyle::State_Raised; + + // Primary button will be 'More Information' + primaryButton.rect = option.rect; + primaryButton.text = tr("Open Browser"); + right = secondaryButton.rect.left() - rightMargin; + left = secondaryButton.rect.left() - leftMargin; + primaryButton.rect.setLeft(left - fm.width(primaryButton.text)); + primaryButton.rect.setRight(right); + primaryButton.rect.setTop(top); + primaryButton.rect.setHeight(_buttonHeight); + primaryButton.features |= QStyleOptionButton::DefaultButton; + primaryButton.state |= QStyle::State_Raised; + // save info to be able to filter mouse clicks _buttonHeight = buttonSize; _spaceBetweenButtons = leftMargin; @@ -200,7 +237,7 @@ void ActivityItemDelegate::paint(QPainter *painter, const QStyleOptionViewItem & painter->drawText(actionTextBox, elidedAction); // draw the buttons - if(activityType == Activity::Type::NotificationType){ + if(activityType == Activity::Type::NotificationType || activityType == Activity::Type::ErrorType){ QApplication::style()->drawControl(QStyle::CE_PushButton, &primaryButton, painter); QApplication::style()->drawControl(QStyle::CE_PushButton, &secondaryButton, painter); } @@ -235,7 +272,8 @@ void ActivityItemDelegate::paint(QPainter *painter, const QStyleOptionViewItem & bool ActivityItemDelegate::editorEvent(QEvent *event, QAbstractItemModel *model, const QStyleOptionViewItem &option, const QModelIndex &index) { - if(qvariant_cast<Activity::Type>(index.data(ActionRole)) == Activity::Type::NotificationType){ + if(qvariant_cast<Activity::Type>(index.data(ActionRole)) == Activity::Type::NotificationType + || qvariant_cast<Activity::Type>(index.data(ActionRole)) == Activity::Type::ErrorType){ if (event->type() == QEvent::MouseButtonRelease){ QMouseEvent *mouseEvent = (QMouseEvent*)event; if(mouseEvent){ diff --git a/src/gui/activitylistmodel.cpp b/src/gui/activitylistmodel.cpp index 562e7f33d..86ec1d95b 100644 --- a/src/gui/activitylistmodel.cpp +++ b/src/gui/activitylistmodel.cpp @@ -29,6 +29,8 @@ #include "activitydata.h" #include "activitylistmodel.h" +#include "theme.h" + #include "servernotificationhandler.h" namespace OCC { @@ -83,15 +85,24 @@ QVariant ActivityListModel::data(const QModelIndex &index, int role) const return customList; break; } -// case ActivityItemDelegate::UserIconRole: -// return QIcon(QLatin1String(":/client/resources/account.png")); -// break; case ActivityItemDelegate::ActionIconRole: if(a._type == Activity::NotificationType){ QIcon cachedIcon = ServerNotificationHandler::iconCache.value(a._id); if(!cachedIcon.isNull()) return cachedIcon; else return QIcon(QLatin1String(":/client/resources/bell.svg")); + } else if(a._type == Activity::ErrorType){ + if(a._status == SyncFileItem::NormalError + || a._status == SyncFileItem::FatalError + || a._status == SyncFileItem::DetailError + || a._status == SyncFileItem::BlacklistedError) { + return Theme::instance()->syncStateIcon(SyncResult::Error); + } else if(a._status == SyncFileItem::SoftError + || a._status == SyncFileItem::FileIgnored + || a._status == SyncFileItem::Conflict + || a._status == SyncFileItem::Restoration){ + return Theme::instance()->syncStateIcon(SyncResult::Problem); + } } else return QIcon(QLatin1String(":/client/resources/activity.png")); return QVariant(); break; @@ -204,7 +215,12 @@ void ActivityListModel::slotActivitiesReceived(const QJsonDocument &json, int st combineActivityLists(); } -void ActivityListModel::addToActivityList(Activity activity) { +void ActivityListModel::addErrorToActivityList(Activity activity) { + _notificationErrorsLists.prepend(activity); + combineActivityLists(); +} + +void ActivityListModel::addNotificationToActivityList(Activity activity) { _notificationLists.prepend(activity); combineActivityLists(); } @@ -219,6 +235,9 @@ void ActivityListModel::combineActivityLists() { ActivityList resultList; + std::sort(_notificationErrorsLists.begin(), _notificationErrorsLists.end()); + resultList.append(_notificationErrorsLists); + std::sort(_notificationLists.begin(), _notificationLists.end()); resultList.append(_notificationLists); diff --git a/src/gui/activitylistmodel.h b/src/gui/activitylistmodel.h index ff110789e..1678cfd1e 100644 --- a/src/gui/activitylistmodel.h +++ b/src/gui/activitylistmodel.h @@ -47,7 +47,8 @@ public: void fetchMore(const QModelIndex &) Q_DECL_OVERRIDE; ActivityList activityList() { return _finalList; } - void addToActivityList(Activity activity); + void addNotificationToActivityList(Activity activity); + void addErrorToActivityList(Activity activity); void removeFromActivityList(int row); public slots: @@ -66,6 +67,7 @@ private: ActivityList _activityLists; ActivityList _notificationLists; + ActivityList _notificationErrorsLists; ActivityList _finalList; AccountState *_accountState; bool _currentlyFetching = true; diff --git a/src/gui/activitywidget.cpp b/src/gui/activitywidget.cpp index 7d733cd55..7d5b718b8 100644 --- a/src/gui/activitywidget.cpp +++ b/src/gui/activitywidget.cpp @@ -95,11 +95,17 @@ ActivityWidget::ActivityWidget(AccountState *accountState, QWidget *parent) connect(delegate, &ActivityItemDelegate::primaryButtonClickedOnItemView, this, &ActivityWidget::slotPrimaryButtonClickedOnListView); connect(delegate, &ActivityItemDelegate::secondaryButtonClickedOnItemView, this, &ActivityWidget::slotSecondaryButtonClickedOnListView); - //connect(this, &ActivityWidget::sendNotificationRequest, this, &ActivityWidget::slotSendNotificationRequest); connect(_ui->_activityList, &QListView::activated, this, &ActivityWidget::slotOpenFile); - connect(&_removeTimer, &QTimer::timeout, this, &ActivityWidget::slotCheckToCleanWidgets); + + 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); } @@ -108,6 +114,75 @@ ActivityWidget::~ActivityWidget() delete _ui; } +void ActivityWidget::slotProgressInfo(const QString &folder, const ProgressInfo &progress) +{ + if (progress.status() == ProgressInfo::Starting) { + // The sync is restarting, clean the old items + //cleanItems(folder); + } +} + +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 (error) + if(folderInstance->accountState() == _accountState){ + Activity activity; + activity._type = Activity::ErrorType; + activity._dateTime = QDateTime::fromString(QDateTime::currentDateTime().toString(), Qt::ISODate); + activity._subject = item->_errorString; + qDebug() << "TOTAL " << folder; + activity._message = item->_originalFile; + activity._link = folderInstance->remotePath(); + activity._status = item->_status; + activity._accName = folderInstance->accountState()->account()->displayName(); + activity._file = item->_file; + + ActivityLink al; + al._label = tr("Open Folder"); + al._link = folderInstance->path(); + al._verb = ""; + al._isPrimary = true; + activity._links.append(al); + + _model->addErrorToActivityList(activity); + // add error widget + } +} + +void ActivityWidget::addError(const QString &folderAlias, const QString &message, + ErrorCategory category) +{ + auto folderInstance = FolderMan::instance()->folder(folderAlias); + if (!folderInstance) + return; + + if(folderInstance->accountState() == _accountState){ + Activity activity; + activity._type = Activity::ErrorType; + activity._dateTime = QDateTime::fromString(QDateTime::currentDateTime().toString(), Qt::ISODate); + activity._subject = message; + activity._message = folderInstance->shortGuiLocalPath(); + activity._link = folderInstance->shortGuiLocalPath(); + activity._status = SyncResult::Error; + activity._accName = folderInstance->accountState()->account()->displayName(); + + 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); + } + + _model->addErrorToActivityList(activity); + } +} + + void ActivityWidget::slotPrimaryButtonClickedOnListView(const QModelIndex &index){ QUrl link = qvariant_cast<QString>(index.data(ActivityItemDelegate::LinkRole)); if(!link.isEmpty()) @@ -121,20 +196,32 @@ void ActivityWidget::slotSecondaryButtonClickedOnListView(const QModelIndex &ind actionLinks << qvariant_cast<ActivityLink>(customItem); } - const QString accountName = index.data(ActivityItemDelegate::AccountRole).toString(); - if(actionLinks.size() == 1){ - if(actionLinks.at(0)._verb == "DELETE") - slotSendNotificationRequest(index.data(ActivityItemDelegate::AccountRole).toString(), actionLinks.at(0)._link, actionLinks.at(0)._verb, index.row()); - } else if(actionLinks.size() > 1){ - QMenu menu; - 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); + if(qvariant_cast<Activity::Type>(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") + slotSendNotificationRequest(index.data(ActivityItemDelegate::AccountRole).toString(), actionLinks.at(0)._link, actionLinks.at(0)._verb, index.row()); + } else if(actionLinks.size() > 1){ + QMenu menu; + 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()); + } + } + + if(qvariant_cast<Activity::Type>(index.data(ActivityItemDelegate::ActionRole)) == Activity::Type::ErrorType){ + QString fileName = index.data(ActivityItemDelegate::PathRole).toString(); + if (Folder *folder = FolderMan::instance()->folderForPath(actionLinks.first()._link)) { + QString fullPath = folder->path() + fileName; + if (QFile(fullPath).exists()) { + showInFileManager(fullPath); + } } - menu.exec(QCursor::pos()); } } @@ -369,7 +456,7 @@ void ActivityWidget::slotBuildNotificationDisplay(const ActivityList &list) } } - _model->addToActivityList(activity); + _model->addNotificationToActivityList(activity); } } @@ -566,11 +653,13 @@ ActivitySettings::ActivitySettings(AccountState *accountState, QWidget *parent) connect(_activityWidget, &ActivityWidget::guiLog, this, &ActivitySettings::guiLog); connect(_activityWidget, &ActivityWidget::newNotification, this, &ActivitySettings::slotShowActivityTab); -// _protocolWidget = new ProtocolWidget(this); + _protocolWidget = new ProtocolWidget(this); + _vbox->addWidget(_protocolWidget); // _protocolTabId = _tab->addTab(_protocolWidget, Theme::instance()->syncStateIcon(SyncResult::Success), tr("Sync Protocol")); // connect(_protocolWidget, &ProtocolWidget::copyToClipboard, this, &ActivitySettings::slotCopyToClipboard); -// _issuesWidget = new IssuesWidget(this); + _issuesWidget = new IssuesWidget(this); + _vbox->addWidget(_issuesWidget); // _syncIssueTabId = _tab->addTab(_issuesWidget, Theme::instance()->syncStateIcon(SyncResult::Problem), QString()); // slotShowIssueItemCount(0); // to display the label. // connect(_issuesWidget, &IssuesWidget::issueCountUpdated, diff --git a/src/gui/activitywidget.h b/src/gui/activitywidget.h index c451380cc..e0ab6ff47 100644 --- a/src/gui/activitywidget.h +++ b/src/gui/activitywidget.h @@ -79,6 +79,12 @@ public slots: void slotRemoveAccount(); void slotAccountActivityStatus(int statusCode); void slotRequestCleanupAndBlacklist(const Activity &blacklistActivity); + // + 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 slotOpenFile(QTreeWidgetItem *item, int); + signals: void guiLog(const QString &, const QString &); @@ -164,6 +170,8 @@ private: bool event(QEvent *e) Q_DECL_OVERRIDE; ActivityWidget *_activityWidget; + ProtocolWidget *_protocolWidget; + IssuesWidget *_issuesWidget; QProgressIndicator *_progressIndicator; QTimer _notificationCheckTimer; QHash<AccountState *, QElapsedTimer> _timeSinceLastCheck;