Merge pull request #491 from nextcloud/fix-activities-v2
Fix activities v2
10
client.qrc
|
@ -22,6 +22,14 @@
|
|||
<file>resources/account.png</file>
|
||||
<file>resources/more.svg</file>
|
||||
<file>resources/delete.png</file>
|
||||
<file>resources/bell.png</file>
|
||||
<file>resources/close.svg</file>
|
||||
<file>resources/bell.svg</file>
|
||||
<file>resources/link.svg</file>
|
||||
<file>resources/files.svg</file>
|
||||
<file>resources/folder-grey.png</file>
|
||||
<file>resources/state-error.svg</file>
|
||||
<file>resources/state-warning.svg</file>
|
||||
<file>resources/folder.svg</file>
|
||||
</qresource>
|
||||
<qresource prefix="/"/>
|
||||
</RCC>
|
||||
|
|
Before Width: | Height: | Size: 495 B |
4
resources/bell.svg
Normal file
|
@ -0,0 +1,4 @@
|
|||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<svg xmlns="http://www.w3.org/2000/svg" height="16" width="16" version="1.1" viewBox="0 0 16 16">
|
||||
<path d="m8 2c-0.5523 0-1 0.4477-1 1 0 0.0472 0.021 0.0873 0.0273 0.1328-1.7366 0.4362-3.0273 1.9953-3.0273 3.8672v2l-1 1v1h10v-1l-1-1v-2c0-1.8719-1.291-3.431-3.0273-3.8672 0.0063-0.0455 0.0273-0.0856 0.0273-0.1328 0-0.5523-0.4477-1-1-1zm-2 10c0 1.1046 0.8954 2 2 2s2-0.8954 2-2z" fill="#000"/>
|
||||
</svg>
|
After Width: | Height: | Size: 456 B |
4
resources/close.svg
Normal file
|
@ -0,0 +1,4 @@
|
|||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<svg xmlns="http://www.w3.org/2000/svg" height="16" width="16" version="1.1">
|
||||
<path d="m12.95 11.536l-1.414 1.414-3.536-3.536-3.535 3.536-1.415-1.414 3.536-3.536-3.536-3.536 1.415-1.414 3.535 3.536 3.516-3.555 1.434 1.434-3.536 3.535z"/>
|
||||
</svg>
|
After Width: | Height: | Size: 301 B |
1
resources/files.svg
Normal file
|
@ -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>
|
After Width: | Height: | Size: 220 B |
1
resources/folder.svg
Normal file
|
@ -0,0 +1 @@
|
|||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16" width="16" version="1.1" height="16"><path fill="#0082c9" d="m1.5 2c-0.25 0-0.5 0.25-0.5 0.5v11c0 0.26 0.24 0.5 0.5 0.5h13c0.26 0 0.5-0.241 0.5-0.5v-9c0-0.25-0.25-0.5-0.5-0.5h-6.5l-2-2z"/></svg>
|
After Width: | Height: | Size: 255 B |
1
resources/link.svg
Normal file
|
@ -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>
|
After Width: | Height: | Size: 851 B |
1
resources/state-error.svg
Normal file
|
@ -0,0 +1 @@
|
|||
<svg width="16" height="16" version="1.1" viewBox="0 0 4.2333 4.2333" xmlns="http://www.w3.org/2000/svg"><g transform="matrix(.87498 0 0 .87498 .26458 -255.9)"><circle cx="2.1167" cy="294.88" r="2.1167" fill="#d40000" stroke-width=".25066"/><path d="m1.1839 293.95 1.8656 1.8656" fill="none" stroke="#fff" stroke-linecap="round" stroke-linejoin="round" stroke-width=".52917"/><path d="m3.0495 293.95-1.8656 1.8656" fill="none" stroke="#fff" stroke-linecap="round" stroke-linejoin="round" stroke-width=".52917"/></g></svg>
|
After Width: | Height: | Size: 522 B |
1
resources/state-warning.svg
Normal file
|
@ -0,0 +1 @@
|
|||
<svg width="16" height="16" version="1.1" viewBox="0 0 4.2333 4.2333" xmlns="http://www.w3.org/2000/svg"><g transform="matrix(.87498 0 0 .87498 .26458 -255.9)"><circle cx="2.1167" cy="294.88" r="2.1167" fill="#e0d92d" stroke-width=".25066"/><path d="m2.1167 293.83v1.3156" fill="none" stroke="#fff" stroke-linecap="round" stroke-linejoin="round" stroke-width=".52917"/><circle cx="2.1103" cy="295.95" r=".33073" fill="#fcffff"/></g></svg>
|
After Width: | Height: | Size: 439 B |
|
@ -22,8 +22,6 @@ set(client_UI_SRCS
|
|||
generalsettings.ui
|
||||
ignorelisteditor.ui
|
||||
networksettings.ui
|
||||
protocolwidget.ui
|
||||
issueswidget.ui
|
||||
activitywidget.ui
|
||||
synclogdialog.ui
|
||||
settingsdialog.ui
|
||||
|
@ -70,8 +68,6 @@ set(client_SRCS
|
|||
openfilemanager.cpp
|
||||
owncloudgui.cpp
|
||||
owncloudsetupwizard.cpp
|
||||
protocolwidget.cpp
|
||||
issueswidget.cpp
|
||||
activitydata.cpp
|
||||
activitylistmodel.cpp
|
||||
activitywidget.cpp
|
||||
|
@ -102,6 +98,7 @@ set(client_SRCS
|
|||
servernotificationhandler.cpp
|
||||
guiutility.cpp
|
||||
elidedlabel.cpp
|
||||
iconjob.cpp
|
||||
creds/credentialsfactory.cpp
|
||||
creds/httpcredentialsgui.cpp
|
||||
creds/oauth.cpp
|
||||
|
|
|
@ -16,6 +16,7 @@
|
|||
#define ACTIVITYDATA_H
|
||||
|
||||
#include <QtCore>
|
||||
#include <QIcon>
|
||||
|
||||
namespace OCC {
|
||||
/**
|
||||
|
@ -48,18 +49,25 @@ public:
|
|||
|
||||
enum Type {
|
||||
ActivityType,
|
||||
NotificationType
|
||||
NotificationType,
|
||||
SyncResultType,
|
||||
SyncFileItemType
|
||||
};
|
||||
|
||||
Type _type;
|
||||
qlonglong _id;
|
||||
QString _objectType;
|
||||
QString _subject;
|
||||
QString _message;
|
||||
QString _folder;
|
||||
QString _file;
|
||||
QUrl _link;
|
||||
QDateTime _dateTime;
|
||||
QString _accName;
|
||||
|
||||
// Stores information about the error
|
||||
int _status;
|
||||
|
||||
QVector<ActivityLink> _links;
|
||||
/**
|
||||
* @brief Sort operator to sort the list youngest first.
|
||||
|
@ -85,4 +93,7 @@ bool operator<(const Activity &rhs, const Activity &lhs);
|
|||
typedef QList<Activity> ActivityList;
|
||||
}
|
||||
|
||||
Q_DECLARE_METATYPE(OCC::Activity::Type)
|
||||
Q_DECLARE_METATYPE(OCC::ActivityLink)
|
||||
|
||||
#endif // ACTIVITYDATA_H
|
||||
|
|
|
@ -18,6 +18,7 @@
|
|||
#include "folderstatusmodel.h"
|
||||
#include "folderman.h"
|
||||
#include "accountstate.h"
|
||||
#include "activitydata.h"
|
||||
#include <theme.h>
|
||||
#include <account.h>
|
||||
|
||||
|
@ -29,6 +30,13 @@ 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");
|
||||
|
||||
int ActivityItemDelegate::iconHeight()
|
||||
{
|
||||
|
@ -51,9 +59,9 @@ int ActivityItemDelegate::rowHeight()
|
|||
QFont f = opt.font;
|
||||
QFontMetrics fm(f);
|
||||
|
||||
_margin = fm.height() / 4;
|
||||
_margin = fm.height() / 2;
|
||||
}
|
||||
return iconHeight() + 2 * _margin;
|
||||
return iconHeight() + 5 * _margin;
|
||||
}
|
||||
|
||||
QSize ActivityItemDelegate::sizeHint(const QStyleOptionViewItem &option,
|
||||
|
@ -68,95 +76,246 @@ void ActivityItemDelegate::paint(QPainter *painter, const QStyleOptionViewItem &
|
|||
const QModelIndex &index) const
|
||||
{
|
||||
QStyledItemDelegate::paint(painter, option, index);
|
||||
|
||||
QFont font = option.font;
|
||||
|
||||
QFontMetrics fm(font);
|
||||
int margin = fm.height() / 4;
|
||||
|
||||
int margin = fm.height() / 2.5;
|
||||
painter->save();
|
||||
int iconSize = 16;
|
||||
int iconOffset = qRound(fm.height() / 4.0 * 7.0);
|
||||
int offset = 4;
|
||||
|
||||
// get the data
|
||||
Activity::Type activityType = qvariant_cast<Activity::Type>(index.data(ActionRole));
|
||||
QIcon actionIcon = qvariant_cast<QIcon>(index.data(ActionIconRole));
|
||||
QIcon userIcon = qvariant_cast<QIcon>(index.data(UserIconRole));
|
||||
QString objectType = qvariant_cast<QString>(index.data(ObjectTypeRole));
|
||||
QString actionText = qvariant_cast<QString>(index.data(ActionTextRole));
|
||||
QString pathText = qvariant_cast<QString>(index.data(PathRole));
|
||||
|
||||
QString remoteLink = qvariant_cast<QString>(index.data(LinkRole));
|
||||
QString messageText = qvariant_cast<QString>(index.data(MessageRole));
|
||||
QList<QVariant> customList = index.data(ActionsLinksRole).toList();
|
||||
QString timeText = qvariant_cast<QString>(index.data(PointInTimeRole));
|
||||
QString accountRole = qvariant_cast<QString>(index.data(AccountRole));
|
||||
bool accountOnline = qvariant_cast<bool>(index.data(AccountConnectedRole));
|
||||
|
||||
// activity/notification icons
|
||||
QRect actionIconRect = option.rect;
|
||||
QRect userIconRect = 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));
|
||||
|
||||
int iconHeight = qRound(fm.height() / 5.0 * 8.0);
|
||||
int iconWidth = iconHeight;
|
||||
// subject text rect
|
||||
QRect actionTextBox = actionIconRect;
|
||||
int actionTextBoxWidth = fm.width(actionText);
|
||||
actionTextBox.setTop(option.rect.top() + margin + offset/2);
|
||||
actionTextBox.setHeight(fm.height());
|
||||
actionTextBox.setLeft(actionIconRect.right() + margin);
|
||||
actionTextBox.setRight(actionTextBox.left() + actionTextBoxWidth + margin);
|
||||
|
||||
actionIconRect.setLeft(option.rect.left() + margin);
|
||||
actionIconRect.setWidth(iconWidth);
|
||||
actionIconRect.setHeight(iconHeight);
|
||||
actionIconRect.setTop(actionIconRect.top() + margin);
|
||||
userIconRect.setLeft(actionIconRect.right() + margin);
|
||||
userIconRect.setWidth(iconWidth);
|
||||
userIconRect.setHeight(iconHeight);
|
||||
userIconRect.setTop(actionIconRect.top());
|
||||
// message text rect
|
||||
QRect messageTextBox = actionTextBox;
|
||||
int messageTextWidth = fm.width(messageText);
|
||||
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());
|
||||
}
|
||||
|
||||
int textTopOffset = qRound((iconHeight - fm.height()) / 2.0);
|
||||
// time rect
|
||||
QRect timeBox;
|
||||
int timeBoxWidth = fm.boundingRect(QLatin1String("4 hour(s) ago on longlongdomain.org")).width(); // FIXME.
|
||||
timeBox.setTop(actionIconRect.top() + textTopOffset);
|
||||
timeBox.setLeft(option.rect.right() - timeBoxWidth - margin);
|
||||
timeBox.setWidth(timeBoxWidth);
|
||||
// time box rect
|
||||
QRect timeBox = messageTextBox;
|
||||
QString timeStr = tr("%1").arg(timeText);
|
||||
int timeTextWidth = fm.width(timeStr);
|
||||
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());
|
||||
timeBox.setRight(timeBox.left() + timeTextWidth + margin);
|
||||
|
||||
QRect actionTextBox = timeBox;
|
||||
actionTextBox.setLeft(userIconRect.right() + margin);
|
||||
actionTextBox.setRight(timeBox.left() - 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;
|
||||
|
||||
/* === start drawing === */
|
||||
QPixmap pm = actionIcon.pixmap(iconWidth, iconHeight, QIcon::Normal);
|
||||
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 = QIcon(QLatin1String(":/client/resources/close.svg"));
|
||||
if(customList.size() > 1)
|
||||
secondaryButton.icon = QIcon(QLatin1String(":/client/resources/more.svg"));
|
||||
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");
|
||||
|
||||
primaryButton.rect.setLeft(left - margin * 2 - fm.width(primaryButton.text));
|
||||
|
||||
// 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 = QIcon(QLatin1String(":/client/resources/folder.svg"));
|
||||
secondaryButton.iconSize = QSize(iconSize, iconSize);
|
||||
|
||||
// Primary button will be 'open browser'
|
||||
primaryButton.text = tr("Open Browser");
|
||||
primaryButton.rect.setLeft(left - margin * 2 - fm.width(primaryButton.text));
|
||||
|
||||
// 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 = QIcon(QLatin1String(":/client/resources/folder.svg"));
|
||||
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);
|
||||
|
||||
pm = userIcon.pixmap(iconWidth, iconHeight, QIcon::Normal);
|
||||
painter->drawPixmap(QPoint(userIconRect.left(), userIconRect.top()), pm);
|
||||
// change pen color if use is not online
|
||||
QPalette p = option.palette;
|
||||
if(!accountOnline)
|
||||
painter->setPen(p.color(QPalette::Disabled, QPalette::Text));
|
||||
|
||||
// change pen color if the line is selected
|
||||
QPalette::ColorGroup cg = option.state & QStyle::State_Enabled
|
||||
? QPalette::Normal
|
||||
: QPalette::Disabled;
|
||||
if (cg == QPalette::Normal && !(option.state & QStyle::State_Active))
|
||||
cg = QPalette::Inactive;
|
||||
if (option.state & QStyle::State_Selected) {
|
||||
painter->setPen(option.palette.color(cg, QPalette::HighlightedText));
|
||||
} else {
|
||||
painter->setPen(option.palette.color(cg, QPalette::Text));
|
||||
}
|
||||
|
||||
const QString elidedAction = fm.elidedText(actionText, Qt::ElideRight, actionTextBox.width());
|
||||
if (option.state & QStyle::State_Selected)
|
||||
painter->setPen(option.palette.color(cg, QPalette::HighlightedText));
|
||||
else
|
||||
painter->setPen(option.palette.color(cg, 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);
|
||||
|
||||
int atPos = accountRole.indexOf(QLatin1Char('@'));
|
||||
if (atPos > -1) {
|
||||
accountRole.remove(0, atPos + 1);
|
||||
// draw the buttons
|
||||
if(activityType == Activity::Type::NotificationType || activityType == Activity::Type::SyncResultType)
|
||||
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()){
|
||||
painter->setPen(p.color(QPalette::Inactive, QPalette::Text));
|
||||
|
||||
// check if line is selected
|
||||
if (option.state & QStyle::State_Selected)
|
||||
painter->setPen(option.palette.color(cg, QPalette::HighlightedText));
|
||||
|
||||
const QString elidedMessage = fm.elidedText(messageText, Qt::ElideRight, spaceLeftForText);
|
||||
painter->drawText(messageTextBox, elidedMessage);
|
||||
}
|
||||
|
||||
QString timeStr;
|
||||
if (accountOnline) {
|
||||
timeStr = tr("%1 on %2").arg(timeText, accountRole);
|
||||
} else {
|
||||
timeStr = tr("%1 on %2 (disconnected)").arg(timeText, accountRole);
|
||||
QPalette p = option.palette;
|
||||
painter->setPen(p.color(QPalette::Disabled, QPalette::Text));
|
||||
}
|
||||
const QString elidedTime = fm.elidedText(timeStr, Qt::ElideRight, timeBox.width());
|
||||
// change pen color for the time
|
||||
painter->setPen(p.color(QPalette::Disabled, QPalette::Text));
|
||||
|
||||
// check if line is selected
|
||||
if (option.state & QStyle::State_Selected)
|
||||
painter->setPen(option.palette.color(cg, QPalette::HighlightedText));
|
||||
|
||||
// draw the time
|
||||
const QString elidedTime = fm.elidedText(timeStr, 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<Activity::Type>(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);
|
||||
}
|
||||
|
||||
|
|
|
@ -15,6 +15,9 @@
|
|||
|
||||
#pragma once
|
||||
#include <QStyledItemDelegate>
|
||||
#include <QMouseEvent>
|
||||
|
||||
class QMouseEvent;
|
||||
|
||||
namespace OCC {
|
||||
|
||||
|
@ -29,11 +32,16 @@ public:
|
|||
enum datarole { ActionIconRole = Qt::UserRole + 1,
|
||||
UserIconRole,
|
||||
AccountRole,
|
||||
ObjectTypeRole,
|
||||
ActionsLinksRole,
|
||||
ActionTextRole,
|
||||
ActionRole,
|
||||
MessageRole,
|
||||
PathRole,
|
||||
LinkRole,
|
||||
PointInTimeRole,
|
||||
AccountConnectedRole };
|
||||
AccountConnectedRole,
|
||||
SyncFileStatusRole };
|
||||
|
||||
void paint(QPainter *, const QStyleOptionViewItem &, const QModelIndex &) const Q_DECL_OVERRIDE;
|
||||
QSize sizeHint(const QStyleOptionViewItem &, const QModelIndex &) const Q_DECL_OVERRIDE;
|
||||
|
@ -43,9 +51,20 @@ public:
|
|||
static int rowHeight();
|
||||
static int iconHeight();
|
||||
|
||||
signals:
|
||||
void primaryButtonClickedOnItemView(const QModelIndex &index);
|
||||
void secondaryButtonClickedOnItemView(const QModelIndex &index);
|
||||
|
||||
private:
|
||||
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;
|
||||
};
|
||||
|
||||
} // namespace OCC
|
||||
|
|
|
@ -29,12 +29,17 @@
|
|||
#include "activitydata.h"
|
||||
#include "activitylistmodel.h"
|
||||
|
||||
#include "theme.h"
|
||||
|
||||
#include "servernotificationhandler.h"
|
||||
|
||||
namespace OCC {
|
||||
|
||||
Q_LOGGING_CATEGORY(lcActivity, "nextcloud.gui.activity", QtInfoMsg)
|
||||
|
||||
ActivityListModel::ActivityListModel(QWidget *parent)
|
||||
ActivityListModel::ActivityListModel(AccountState *accountState, QWidget *parent)
|
||||
: QAbstractListModel(parent)
|
||||
, _accountState(accountState)
|
||||
{
|
||||
}
|
||||
|
||||
|
@ -42,40 +47,87 @@ QVariant ActivityListModel::data(const QModelIndex &index, int role) const
|
|||
{
|
||||
Activity a;
|
||||
|
||||
// filter the get action here
|
||||
// send only the text of the get action
|
||||
// if there is more than one send the icon? the ...
|
||||
|
||||
if (!index.isValid())
|
||||
return QVariant();
|
||||
|
||||
a = _finalList.at(index.row());
|
||||
AccountStatePtr ast = AccountManager::instance()->account(a._accName);
|
||||
if (!ast)
|
||||
if (!ast && _accountState != ast.data())
|
||||
return QVariant();
|
||||
QStringList list;
|
||||
|
||||
switch (role) {
|
||||
case ActivityItemDelegate::PathRole:
|
||||
if(!a._file.isEmpty()){
|
||||
list = FolderMan::instance()->findFileInLocalFolders(a._file, ast->account());
|
||||
auto folder = FolderMan::instance()->folder(a._folder);
|
||||
QString relPath(a._file);
|
||||
if(folder) relPath.prepend(folder->remotePath());
|
||||
list = FolderMan::instance()->findFileInLocalFolders(relPath, ast->account());
|
||||
if (list.count() > 0) {
|
||||
return QVariant(list.at(0));
|
||||
}
|
||||
// File does not exist anymore? Let's try to open its path
|
||||
list = FolderMan::instance()->findFileInLocalFolders(QFileInfo(a._file).path(), ast->account());
|
||||
list = FolderMan::instance()->findFileInLocalFolders(QFileInfo(relPath).path(), ast->account());
|
||||
if (list.count() > 0) {
|
||||
return QVariant(list.at(0));
|
||||
}
|
||||
}
|
||||
return QVariant();
|
||||
break;
|
||||
case ActivityItemDelegate::ActionsLinksRole:{
|
||||
QList<QVariant> customList;
|
||||
foreach (ActivityLink customItem, a._links) {
|
||||
QVariant customVariant;
|
||||
customVariant.setValue(customItem);
|
||||
customList << customVariant;
|
||||
}
|
||||
return customList;
|
||||
break;
|
||||
}
|
||||
case ActivityItemDelegate::ActionIconRole:
|
||||
return QVariant(); // FIXME once the action can be quantified, display on Icon
|
||||
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::SyncResultType){
|
||||
return QIcon(QLatin1String(":/client/resources/state-error.svg"));
|
||||
} else if(a._type == Activity::SyncFileItemType){
|
||||
if(a._status == SyncFileItem::NormalError
|
||||
|| a._status == SyncFileItem::FatalError
|
||||
|| a._status == SyncFileItem::DetailError
|
||||
|| a._status == SyncFileItem::BlacklistedError) {
|
||||
return QIcon(QLatin1String(":/client/resources/state-error.svg"));
|
||||
} else if(a._status == SyncFileItem::SoftError
|
||||
|| a._status == SyncFileItem::FileIgnored
|
||||
|| a._status == SyncFileItem::Conflict
|
||||
|| a._status == SyncFileItem::Restoration){
|
||||
return QIcon(QLatin1String(":/client/resources/state-warning.svg"));
|
||||
}
|
||||
return QIcon(QLatin1String(":/client/resources/activity.png"));
|
||||
} else return QIcon(QLatin1String(":/client/resources/activity.png"));
|
||||
return QVariant();
|
||||
break;
|
||||
case ActivityItemDelegate::UserIconRole:
|
||||
return QIcon(QLatin1String(":/client/resources/account.png"));
|
||||
case ActivityItemDelegate::ObjectTypeRole:
|
||||
return a._objectType;
|
||||
break;
|
||||
case ActivityItemDelegate::ActionRole:{
|
||||
QVariant type;
|
||||
type.setValue(a._type);
|
||||
return type;
|
||||
break;
|
||||
}
|
||||
case Qt::ToolTipRole:
|
||||
case ActivityItemDelegate::ActionTextRole:
|
||||
return a._subject;
|
||||
break;
|
||||
case ActivityItemDelegate::MessageRole:
|
||||
return a._message;
|
||||
break;
|
||||
case ActivityItemDelegate::LinkRole:
|
||||
return a._link;
|
||||
break;
|
||||
|
@ -107,36 +159,31 @@ bool ActivityListModel::canFetchMore(const QModelIndex &) const
|
|||
if (_activityLists.count() == 0)
|
||||
return true;
|
||||
|
||||
for (auto i = _activityLists.begin(); i != _activityLists.end(); ++i) {
|
||||
AccountState *ast = i.key();
|
||||
if (ast && ast->isConnected()) {
|
||||
ActivityList activities = i.value();
|
||||
if (activities.count() == 0 && !_currentlyFetching.contains(ast)) {
|
||||
return true;
|
||||
}
|
||||
if (_accountState && _accountState->isConnected()) {
|
||||
if (_activityLists.count() == 0 && !_currentlyFetching) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
void ActivityListModel::startFetchJob(AccountState *s)
|
||||
void ActivityListModel::startFetchJob()
|
||||
{
|
||||
if (!s->isConnected()) {
|
||||
if (!_accountState->isConnected()) {
|
||||
return;
|
||||
}
|
||||
JsonApiJob *job = new JsonApiJob(s->account(), QLatin1String("ocs/v1.php/cloud/activity"), this);
|
||||
JsonApiJob *job = new JsonApiJob(_accountState->account(), QLatin1String("ocs/v2.php/cloud/activity"), this);
|
||||
QObject::connect(job, &JsonApiJob::jsonReceived,
|
||||
this, &ActivityListModel::slotActivitiesReceived);
|
||||
job->setProperty("AccountStatePtr", QVariant::fromValue<QPointer<AccountState>>(s));
|
||||
|
||||
QUrlQuery params;
|
||||
params.addQueryItem(QLatin1String("page"), QLatin1String("0"));
|
||||
params.addQueryItem(QLatin1String("pagesize"), QLatin1String("100"));
|
||||
job->addQueryParams(params);
|
||||
|
||||
_currentlyFetching.insert(s);
|
||||
qCInfo(lcActivity) << "Start fetching activities for " << s->account()->displayName();
|
||||
_currentlyFetching = true;
|
||||
qCInfo(lcActivity) << "Start fetching activities for " << _accountState->account()->displayName();
|
||||
job->start();
|
||||
}
|
||||
|
||||
|
@ -145,11 +192,11 @@ void ActivityListModel::slotActivitiesReceived(const QJsonDocument &json, int st
|
|||
auto activities = json.object().value("ocs").toObject().value("data").toArray();
|
||||
|
||||
ActivityList list;
|
||||
auto ast = qvariant_cast<QPointer<AccountState>>(sender()->property("AccountStatePtr"));
|
||||
auto ast = _accountState;
|
||||
if (!ast)
|
||||
return;
|
||||
|
||||
_currentlyFetching.remove(ast);
|
||||
_currentlyFetching = 0;
|
||||
|
||||
foreach (auto activ, activities) {
|
||||
auto json = activ.toObject();
|
||||
|
@ -166,23 +213,66 @@ void ActivityListModel::slotActivitiesReceived(const QJsonDocument &json, int st
|
|||
list.append(a);
|
||||
}
|
||||
|
||||
_activityLists[ast] = list;
|
||||
_activityLists = list;
|
||||
|
||||
emit activityJobStatusCode(ast, statusCode);
|
||||
emit activityJobStatusCode(statusCode);
|
||||
|
||||
combineActivityLists();
|
||||
}
|
||||
|
||||
void ActivityListModel::addErrorToActivityList(Activity activity) {
|
||||
qCInfo(lcActivity) << "Error successfully added to the notification list: " << activity._subject;
|
||||
_notificationErrorsLists.prepend(activity);
|
||||
combineActivityLists();
|
||||
}
|
||||
|
||||
void ActivityListModel::addNotificationToActivityList(Activity activity) {
|
||||
qCInfo(lcActivity) << "Notification successfully added to the notification list: " << activity._subject;
|
||||
_notificationLists.prepend(activity);
|
||||
combineActivityLists();
|
||||
}
|
||||
|
||||
void ActivityListModel::removeActivityFromActivityList(int row) {
|
||||
Activity activity = _finalList.at(row);
|
||||
removeActivityFromActivityList(activity);
|
||||
}
|
||||
|
||||
void ActivityListModel::removeActivityFromActivityList(Activity activity) {
|
||||
qCInfo(lcActivity) << "Activity/Notification/Error successfully dismissed: " << activity._subject;
|
||||
qCInfo(lcActivity) << "Trying to remove Activity/Notification/Error from view... ";
|
||||
|
||||
int index = -1;
|
||||
if(activity._type == Activity::ActivityType){
|
||||
index = _activityLists.indexOf(activity);
|
||||
if(index != -1) _activityLists.removeAt(index);
|
||||
} else if(activity._type == Activity::NotificationType){
|
||||
index = _notificationLists.indexOf(activity);
|
||||
if(index != -1) _notificationLists.removeAt(index);
|
||||
} else {
|
||||
index = _notificationErrorsLists.indexOf(activity);
|
||||
if(index != -1) _notificationErrorsLists.removeAt(index);
|
||||
}
|
||||
|
||||
if(index != -1){
|
||||
qCInfo(lcActivity) << "Activity/Notification/Error successfully removed from the list.";
|
||||
qCInfo(lcActivity) << "Updating Activity/Notification/Error view.";
|
||||
combineActivityLists();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
void ActivityListModel::combineActivityLists()
|
||||
{
|
||||
ActivityList resultList;
|
||||
|
||||
foreach (ActivityList list, _activityLists.values()) {
|
||||
resultList.append(list);
|
||||
}
|
||||
std::sort(_notificationErrorsLists.begin(), _notificationErrorsLists.end());
|
||||
resultList.append(_notificationErrorsLists);
|
||||
|
||||
std::sort(resultList.begin(), resultList.end());
|
||||
std::sort(_notificationLists.begin(), _notificationLists.end());
|
||||
resultList.append(_notificationLists);
|
||||
|
||||
std::sort(_activityLists.begin(), _activityLists.end());
|
||||
resultList.append(_activityLists);
|
||||
|
||||
beginResetModel();
|
||||
_finalList.clear();
|
||||
|
@ -195,42 +285,22 @@ void ActivityListModel::combineActivityLists()
|
|||
|
||||
void ActivityListModel::fetchMore(const QModelIndex &)
|
||||
{
|
||||
QList<AccountStatePtr> accounts = AccountManager::instance()->accounts();
|
||||
|
||||
foreach (const AccountStatePtr &asp, accounts) {
|
||||
if (!_activityLists.contains(asp.data()) && asp->isConnected()) {
|
||||
_activityLists[asp.data()] = ActivityList();
|
||||
startFetchJob(asp.data());
|
||||
}
|
||||
if (_accountState->isConnected()) {
|
||||
_activityLists = ActivityList();
|
||||
startFetchJob();
|
||||
}
|
||||
}
|
||||
|
||||
void ActivityListModel::slotRefreshActivity(AccountState *ast)
|
||||
void ActivityListModel::slotRefreshActivity()
|
||||
{
|
||||
if (ast && _activityLists.contains(ast)) {
|
||||
_activityLists.remove(ast);
|
||||
}
|
||||
startFetchJob(ast);
|
||||
_activityLists.clear();
|
||||
startFetchJob();
|
||||
}
|
||||
|
||||
void ActivityListModel::slotRemoveAccount(AccountState *ast)
|
||||
void ActivityListModel::slotRemoveAccount()
|
||||
{
|
||||
if (_activityLists.contains(ast)) {
|
||||
int i = 0;
|
||||
const QString accountToRemove = ast->account()->displayName();
|
||||
|
||||
QMutableListIterator<Activity> it(_finalList);
|
||||
|
||||
while (it.hasNext()) {
|
||||
Activity activity = it.next();
|
||||
if (activity._accName == accountToRemove) {
|
||||
beginRemoveRows(QModelIndex(), i, i + 1);
|
||||
it.remove();
|
||||
endRemoveRows();
|
||||
}
|
||||
}
|
||||
_activityLists.remove(ast);
|
||||
_currentlyFetching.remove(ast);
|
||||
}
|
||||
_finalList.clear();
|
||||
_activityLists.clear();
|
||||
_currentlyFetching = false;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -38,7 +38,7 @@ class ActivityListModel : public QAbstractListModel
|
|||
{
|
||||
Q_OBJECT
|
||||
public:
|
||||
explicit ActivityListModel(QWidget *parent = 0);
|
||||
explicit ActivityListModel(AccountState *accountState, QWidget *parent = 0);
|
||||
|
||||
QVariant data(const QModelIndex &index, int role) const Q_DECL_OVERRIDE;
|
||||
int rowCount(const QModelIndex &parent = QModelIndex()) const Q_DECL_OVERRIDE;
|
||||
|
@ -47,24 +47,32 @@ public:
|
|||
void fetchMore(const QModelIndex &) Q_DECL_OVERRIDE;
|
||||
|
||||
ActivityList activityList() { return _finalList; }
|
||||
ActivityList errorsList() { return _notificationErrorsLists; }
|
||||
void addNotificationToActivityList(Activity activity);
|
||||
void addErrorToActivityList(Activity activity);
|
||||
void removeActivityFromActivityList(int row);
|
||||
void removeActivityFromActivityList(Activity activity);
|
||||
|
||||
public slots:
|
||||
void slotRefreshActivity(AccountState *ast);
|
||||
void slotRemoveAccount(AccountState *ast);
|
||||
void slotRefreshActivity();
|
||||
void slotRemoveAccount();
|
||||
|
||||
private slots:
|
||||
void slotActivitiesReceived(const QJsonDocument &json, int statusCode);
|
||||
|
||||
signals:
|
||||
void activityJobStatusCode(AccountState *ast, int statusCode);
|
||||
void activityJobStatusCode(int statusCode);
|
||||
|
||||
private:
|
||||
void startFetchJob(AccountState *s);
|
||||
void startFetchJob();
|
||||
void combineActivityLists();
|
||||
|
||||
QMap<AccountState *, ActivityList> _activityLists;
|
||||
ActivityList _activityLists;
|
||||
ActivityList _notificationLists;
|
||||
ActivityList _notificationErrorsLists;
|
||||
ActivityList _finalList;
|
||||
QSet<AccountState *> _currentlyFetching;
|
||||
AccountState *_accountState;
|
||||
bool _currentlyFetching = true;
|
||||
};
|
||||
}
|
||||
#endif // ACTIVITYLISTMODEL_H
|
||||
|
|
|
@ -29,8 +29,6 @@
|
|||
#include "accountstate.h"
|
||||
#include "accountmanager.h"
|
||||
#include "activityitemdelegate.h"
|
||||
#include "protocolwidget.h"
|
||||
#include "issueswidget.h"
|
||||
#include "QProgressIndicator.h"
|
||||
#include "notificationwidget.h"
|
||||
#include "notificationconfirmjob.h"
|
||||
|
@ -38,8 +36,10 @@
|
|||
#include "theme.h"
|
||||
#include "ocsjob.h"
|
||||
#include "configfile.h"
|
||||
|
||||
#include "guiutility.h"
|
||||
#include "socketapi.h"
|
||||
#include "ui_activitywidget.h"
|
||||
#include "syncengine.h"
|
||||
|
||||
#include <climits>
|
||||
|
||||
|
@ -49,10 +49,13 @@
|
|||
|
||||
namespace OCC {
|
||||
|
||||
ActivityWidget::ActivityWidget(QWidget *parent)
|
||||
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);
|
||||
|
||||
|
@ -61,39 +64,36 @@ ActivityWidget::ActivityWidget(QWidget *parent)
|
|||
_ui->_activityList->setMinimumWidth(400);
|
||||
#endif
|
||||
|
||||
_model = new ActivityListModel(this);
|
||||
_model = new ActivityListModel(accountState, this);
|
||||
ActivityItemDelegate *delegate = new ActivityItemDelegate;
|
||||
delegate->setParent(this);
|
||||
_ui->_activityList->setItemDelegate(delegate);
|
||||
_ui->_activityList->setBackgroundRole(QPalette::Background);
|
||||
_ui->_activityList->setAlternatingRowColors(true);
|
||||
_ui->_activityList->setModel(_model);
|
||||
|
||||
_ui->_notifyLabel->hide();
|
||||
_ui->_notifyScroll->hide();
|
||||
|
||||
// Create a widget container for the notifications. The ui file defines
|
||||
// a scroll area that get a widget with a layout as children
|
||||
QWidget *w = new QWidget;
|
||||
_notificationsLayout = new QVBoxLayout;
|
||||
w->setLayout(_notificationsLayout);
|
||||
_notificationsLayout->setAlignment(Qt::AlignTop);
|
||||
_ui->_notifyScroll->setAlignment(Qt::AlignTop);
|
||||
_ui->_notifyScroll->setWidget(w);
|
||||
|
||||
showLabels();
|
||||
|
||||
connect(_model, &ActivityListModel::activityJobStatusCode,
|
||||
this, &ActivityWidget::slotAccountActivityStatus);
|
||||
|
||||
_copyBtn = _ui->_dialogButtonBox->addButton(tr("Copy"), QDialogButtonBox::ActionRole);
|
||||
_copyBtn->setToolTip(tr("Copy the activity list to the clipboard."));
|
||||
connect(_copyBtn, &QAbstractButton::clicked, this, &ActivityWidget::copyToClipboard);
|
||||
_ui->_copyButton->setToolTip(tr("Copy the activity list to the clipboard."));
|
||||
connect(_ui->_copyButton, &QPushButton::click, this, &ActivityWidget::copyToClipboard);
|
||||
|
||||
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(&_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);
|
||||
}
|
||||
|
||||
|
@ -102,39 +102,231 @@ ActivityWidget::~ActivityWidget()
|
|||
delete _ui;
|
||||
}
|
||||
|
||||
void ActivityWidget::slotRefreshActivities(AccountState *ptr)
|
||||
void ActivityWidget::slotProgressInfo(const QString &folder, const ProgressInfo &progress)
|
||||
{
|
||||
_model->slotRefreshActivity(ptr);
|
||||
|
||||
// TODO: this is really not working
|
||||
// if (progress.status() == ProgressInfo::Done
|
||||
// || 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::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::slotRefreshNotifications(AccountState *ptr)
|
||||
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;
|
||||
activity._status = item->_status;
|
||||
activity._dateTime = QDateTime::fromString(QDateTime::currentDateTime().toString(), Qt::ISODate);
|
||||
activity._subject = item->_errorString;
|
||||
activity._message = item->_originalFile;
|
||||
activity._link = folderInstance->accountState()->account()->url();
|
||||
activity._accName = folderInstance->accountState()->account()->displayName();
|
||||
activity._file = item->_file;
|
||||
activity._folder = folder;
|
||||
|
||||
// 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<QString>(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<QString>(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<ActivityLink>(customItem);
|
||||
if(actionLink._label == _accept){
|
||||
qCWarning(lcActivity) << objectType << "action" << actionLink._label << "for" << qvariant_cast<QString>(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<QString>(index.data(ActivityItemDelegate::ActionTextRole));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void ActivityWidget::slotSecondaryButtonClickedOnListView(const QModelIndex &index){
|
||||
QList<QVariant> customList = index.data(ActivityItemDelegate::ActionsLinksRole).toList();
|
||||
QString objectType = index.data(ActivityItemDelegate::ObjectTypeRole).toString();
|
||||
|
||||
QList<ActivityLink> actionLinks;
|
||||
foreach(QVariant customItem, customList){
|
||||
actionLinks << qvariant_cast<ActivityLink>(customItem);
|
||||
}
|
||||
|
||||
if(objectType == _remote_share && actionLinks.first()._label == _accept)
|
||||
actionLinks.removeFirst();
|
||||
|
||||
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"){
|
||||
qCWarning(lcActivity) << "Dismissing Notification/Activity" << qvariant_cast<QString>(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<QString>(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<Activity::Type>(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;
|
||||
ServerNotificationHandler *snh = new ServerNotificationHandler(_accountState);
|
||||
connect(snh, &ServerNotificationHandler::newNotificationList,
|
||||
this, &ActivityWidget::slotBuildNotificationDisplay);
|
||||
|
||||
snh->slotFetchNotifications(ptr);
|
||||
snh->slotFetchNotifications();
|
||||
} else {
|
||||
qCWarning(lcActivity) << "Notification request counter not zero.";
|
||||
}
|
||||
}
|
||||
|
||||
void ActivityWidget::slotRemoveAccount(AccountState *ptr)
|
||||
void ActivityWidget::slotRemoveAccount()
|
||||
{
|
||||
_model->slotRemoveAccount(ptr);
|
||||
_model->slotRemoveAccount();
|
||||
}
|
||||
|
||||
void ActivityWidget::showLabels()
|
||||
{
|
||||
QString t = tr("Server Activities");
|
||||
_ui->_headerLabel->setTextFormat(Qt::RichText);
|
||||
_ui->_headerLabel->setText(t);
|
||||
|
||||
_ui->_notifyLabel->setText(tr("Action Required: Notifications"));
|
||||
|
||||
t.clear();
|
||||
QSetIterator<QString> i(_accountsWithoutActivities);
|
||||
while (i.hasNext()) {
|
||||
|
@ -144,15 +336,15 @@ void ActivityWidget::showLabels()
|
|||
_ui->_bottomLabel->setText(t);
|
||||
}
|
||||
|
||||
void ActivityWidget::slotAccountActivityStatus(AccountState *ast, int statusCode)
|
||||
void ActivityWidget::slotAccountActivityStatus(int statusCode)
|
||||
{
|
||||
if (!(ast && ast->account())) {
|
||||
if (!(_accountState && _accountState->account())) {
|
||||
return;
|
||||
}
|
||||
if (statusCode == 999) {
|
||||
_accountsWithoutActivities.insert(ast->account()->displayName());
|
||||
_accountsWithoutActivities.insert(_accountState->account()->displayName());
|
||||
} else {
|
||||
_accountsWithoutActivities.remove(ast->account()->displayName());
|
||||
_accountsWithoutActivities.remove(_accountState->account()->displayName());
|
||||
}
|
||||
|
||||
checkActivityTabVisibility();
|
||||
|
@ -176,35 +368,33 @@ void ActivityWidget::storeActivityList(QTextStream &ts)
|
|||
foreach (Activity activity, activities) {
|
||||
ts << right
|
||||
// account name
|
||||
<< qSetFieldWidth(30)
|
||||
<< qSetFieldWidth(activity._accName.length())
|
||||
<< activity._accName
|
||||
// separator
|
||||
<< qSetFieldWidth(0) << ","
|
||||
<< qSetFieldWidth(2) << " - "
|
||||
|
||||
// date and time
|
||||
<< qSetFieldWidth(34)
|
||||
<< qSetFieldWidth(activity._dateTime.toString().length())
|
||||
<< activity._dateTime.toString()
|
||||
// separator
|
||||
<< qSetFieldWidth(0) << ","
|
||||
<< qSetFieldWidth(2) << " - "
|
||||
|
||||
// file
|
||||
<< qSetFieldWidth(30)
|
||||
// fileq
|
||||
<< qSetFieldWidth(activity._file.length())
|
||||
<< activity._file
|
||||
// separator
|
||||
<< qSetFieldWidth(0) << ","
|
||||
<< qSetFieldWidth(2) << " - "
|
||||
|
||||
// subject
|
||||
<< qSetFieldWidth(100)
|
||||
<< qSetFieldWidth(activity._subject.length())
|
||||
<< activity._subject
|
||||
// separator
|
||||
<< qSetFieldWidth(0) << ","
|
||||
<< qSetFieldWidth(2) << " - "
|
||||
|
||||
// message (mostly empty)
|
||||
<< qSetFieldWidth(55)
|
||||
<< activity._message
|
||||
//
|
||||
<< qSetFieldWidth(0)
|
||||
<< endl;
|
||||
// message
|
||||
<< qSetFieldWidth(activity._message.length())
|
||||
<< activity._message
|
||||
<< endl;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -215,12 +405,8 @@ void ActivityWidget::checkActivityTabVisibility()
|
|||
_accountsWithoutActivities.count() != accountCount;
|
||||
bool hasNotifications = !_widgetForNotifId.isEmpty();
|
||||
|
||||
_ui->_headerLabel->setVisible(hasAccountsWithActivity);
|
||||
_ui->_activityList->setVisible(hasAccountsWithActivity);
|
||||
|
||||
_ui->_notifyLabel->setVisible(hasNotifications);
|
||||
_ui->_notifyScroll->setVisible(hasNotifications);
|
||||
|
||||
emit hideActivityTab(!hasAccountsWithActivity && !hasNotifications);
|
||||
}
|
||||
|
||||
|
@ -229,9 +415,10 @@ 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 (QFile::exists(fullPath)) {
|
||||
showInFileManager(fullPath);
|
||||
if(!fullPath.isEmpty()){
|
||||
if (QFile::exists(fullPath)) {
|
||||
showInFileManager(fullPath);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -242,10 +429,9 @@ void ActivityWidget::slotOpenFile(QModelIndex indx)
|
|||
// collected.
|
||||
void ActivityWidget::slotBuildNotificationDisplay(const ActivityList &list)
|
||||
{
|
||||
QHash<QString, int> accNotified;
|
||||
QString listAccountName;
|
||||
|
||||
// Whether a new notification widget was added to the notificationLayout.
|
||||
// Whether a new notification was added to the list
|
||||
bool newNotificationShown = false;
|
||||
|
||||
foreach (auto activity, list) {
|
||||
|
@ -254,26 +440,6 @@ void ActivityWidget::slotBuildNotificationDisplay(const ActivityList &list)
|
|||
continue;
|
||||
}
|
||||
|
||||
NotificationWidget *widget = 0;
|
||||
|
||||
if (_widgetForNotifId.contains(activity.ident())) {
|
||||
widget = _widgetForNotifId[activity.ident()];
|
||||
} else {
|
||||
widget = new NotificationWidget(this);
|
||||
connect(widget, &NotificationWidget::sendNotificationRequest,
|
||||
this, &ActivityWidget::slotSendNotificationRequest);
|
||||
connect(widget, &NotificationWidget::requestCleanupAndBlacklist,
|
||||
this, &ActivityWidget::slotRequestCleanupAndBlacklist);
|
||||
|
||||
_notificationsLayout->addWidget(widget);
|
||||
// _ui->_notifyScroll->setMinimumHeight( widget->height());
|
||||
_ui->_notifyScroll->setSizeAdjustPolicy(QAbstractScrollArea::AdjustToContentsOnFirstShow);
|
||||
_widgetForNotifId[activity.ident()] = widget;
|
||||
newNotificationShown = true;
|
||||
}
|
||||
|
||||
widget->setActivity(activity);
|
||||
|
||||
// remember the list account name for the strayCat handling below.
|
||||
listAccountName = activity._accName;
|
||||
|
||||
|
@ -287,22 +453,9 @@ void ActivityWidget::slotBuildNotificationDisplay(const ActivityList &list)
|
|||
if (_guiLogTimer.elapsed() > 60 * 60 * 1000) {
|
||||
_guiLoggedNotifications.clear();
|
||||
}
|
||||
|
||||
if (!_guiLoggedNotifications.contains(activity._id)) {
|
||||
QString host = activity._accName;
|
||||
// store the name of the account that sends the notification to be
|
||||
// able to add it to the tray notification
|
||||
// remove the user name from the account as that is not accurate here.
|
||||
int indx = host.indexOf(QChar('@'));
|
||||
if (indx > -1) {
|
||||
host.remove(0, 1 + indx);
|
||||
}
|
||||
if (!host.isEmpty()) {
|
||||
if (accNotified.contains(host)) {
|
||||
accNotified[host] = accNotified[host] + 1;
|
||||
} else {
|
||||
accNotified[host] = 1;
|
||||
}
|
||||
}
|
||||
newNotificationShown = true;
|
||||
_guiLoggedNotifications.insert(activity._id);
|
||||
|
||||
// Assemble a tray notification for the NEW notification
|
||||
|
@ -314,53 +467,19 @@ void ActivityWidget::slotBuildNotificationDisplay(const ActivityList &list)
|
|||
emit guiLog(activity._subject, activity._accName);
|
||||
}
|
||||
}
|
||||
|
||||
_model->addNotificationToActivityList(activity);
|
||||
}
|
||||
}
|
||||
|
||||
// check if there are widgets that have no corresponding activity from
|
||||
// the server any more. Collect them in a list
|
||||
QList<Activity::Identifier> strayCats;
|
||||
foreach (auto id, _widgetForNotifId.keys()) {
|
||||
NotificationWidget *widget = _widgetForNotifId[id];
|
||||
|
||||
bool found = false;
|
||||
// do not mark widgets of other accounts to delete.
|
||||
if (widget->activity()._accName != listAccountName) {
|
||||
continue;
|
||||
}
|
||||
|
||||
foreach (auto activity, list) {
|
||||
if (activity.ident() == id) {
|
||||
// found an activity
|
||||
found = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (!found) {
|
||||
// the activity does not exist any more.
|
||||
strayCats.append(id);
|
||||
}
|
||||
}
|
||||
|
||||
// .. and now delete all these stray cat widgets.
|
||||
foreach (auto strayCatId, strayCats) {
|
||||
NotificationWidget *widgetToGo = _widgetForNotifId[strayCatId];
|
||||
scheduleWidgetToRemove(widgetToGo, 0);
|
||||
}
|
||||
|
||||
checkActivityTabVisibility();
|
||||
|
||||
if (newNotificationShown) {
|
||||
// restart the gui log timer now that we show a notification
|
||||
// restart the gui log timer now that we show a new notification
|
||||
if(newNotificationShown)
|
||||
_guiLogTimer.start();
|
||||
emit newNotification();
|
||||
}
|
||||
}
|
||||
|
||||
void ActivityWidget::slotSendNotificationRequest(const QString &accountName, const QString &link, const QByteArray &verb)
|
||||
void ActivityWidget::slotSendNotificationRequest(const QString &accountName, const QString &link, const QByteArray &verb, int row)
|
||||
{
|
||||
qCInfo(lcActivity) << "Server Notification Request " << verb << link << "on account" << accountName;
|
||||
NotificationWidget *theSender = qobject_cast<NotificationWidget *>(sender());
|
||||
|
||||
const QStringList validVerbs = QStringList() << "GET"
|
||||
<< "PUT"
|
||||
|
@ -373,7 +492,7 @@ void ActivityWidget::slotSendNotificationRequest(const QString &accountName, con
|
|||
NotificationConfirmJob *job = new NotificationConfirmJob(acc->account());
|
||||
QUrl l(link);
|
||||
job->setLinkAndVerb(l, verb);
|
||||
job->setWidget(theSender);
|
||||
job->setProperty("activityRow", QVariant::fromValue(row));
|
||||
connect(job, &AbstractNetworkJob::networkError,
|
||||
this, &ActivityWidget::slotNotifyNetworkError);
|
||||
connect(job, &NotificationConfirmJob::jobFinished,
|
||||
|
@ -392,9 +511,7 @@ void ActivityWidget::slotSendNotificationRequest(const QString &accountName, con
|
|||
void ActivityWidget::endNotificationRequest(NotificationWidget *widget, int replyCode)
|
||||
{
|
||||
_notificationRequestsRunning--;
|
||||
if (widget) {
|
||||
widget->slotNotificationRequestFinished(replyCode);
|
||||
}
|
||||
slotNotificationRequestFinished(replyCode);
|
||||
}
|
||||
|
||||
void ActivityWidget::slotNotifyNetworkError(QNetworkReply *reply)
|
||||
|
@ -425,7 +542,7 @@ void ActivityWidget::slotNotifyServerFinished(const QString &reply, int replyCod
|
|||
// Add 200 millisecs to the predefined value to make sure that the timer in
|
||||
// widget's method readyToClose() has elapsed.
|
||||
if (replyCode == OCS_SUCCESS_STATUS_CODE || replyCode == OCS_SUCCESS_STATUS_CODE_V2) {
|
||||
scheduleWidgetToRemove(job->widget());
|
||||
//scheduleWidgetToRemove(job->widget());
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -480,57 +597,42 @@ void ActivityWidget::slotCheckToCleanWidgets()
|
|||
if (_widgetsToRemove.isEmpty()) {
|
||||
_removeTimer.stop();
|
||||
}
|
||||
|
||||
// check to see if the whole notification pane should be hidden
|
||||
if (_widgetForNotifId.isEmpty()) {
|
||||
_ui->_notifyLabel->setHidden(true);
|
||||
_ui->_notifyScroll->setHidden(true);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/* ==================================================================== */
|
||||
|
||||
ActivitySettings::ActivitySettings(QWidget *parent)
|
||||
ActivitySettings::ActivitySettings(AccountState *accountState, QWidget *parent)
|
||||
: QWidget(parent)
|
||||
, _accountState(accountState)
|
||||
{
|
||||
QHBoxLayout *hbox = new QHBoxLayout(this);
|
||||
setLayout(hbox);
|
||||
_vbox = new QVBoxLayout(this);
|
||||
setLayout(_vbox);
|
||||
|
||||
// create a tab widget for the three activity views
|
||||
_tab = new QTabWidget(this);
|
||||
hbox->addWidget(_tab);
|
||||
_activityWidget = new ActivityWidget(this);
|
||||
_activityTabId = _tab->addTab(_activityWidget, Theme::instance()->applicationIcon(), tr("Server Activity"));
|
||||
_activityWidget = new ActivityWidget(_accountState, this);
|
||||
|
||||
// set background white
|
||||
QPalette palette;
|
||||
palette.setColor(QPalette::Background, Qt::white);
|
||||
_activityWidget->setAutoFillBackground(true);
|
||||
_activityWidget->setPalette(palette);
|
||||
|
||||
_vbox->insertWidget(1, _activityWidget);
|
||||
connect(_activityWidget, &ActivityWidget::copyToClipboard, this, &ActivitySettings::slotCopyToClipboard);
|
||||
connect(_activityWidget, &ActivityWidget::hideActivityTab, this, &ActivitySettings::setActivityTabHidden);
|
||||
connect(_activityWidget, &ActivityWidget::guiLog, this, &ActivitySettings::guiLog);
|
||||
connect(_activityWidget, &ActivityWidget::newNotification, this, &ActivitySettings::slotShowActivityTab);
|
||||
|
||||
_protocolWidget = new ProtocolWidget(this);
|
||||
_protocolTabId = _tab->addTab(_protocolWidget, Theme::instance()->syncStateIcon(SyncResult::Success), tr("Sync Protocol"));
|
||||
connect(_protocolWidget, &ProtocolWidget::copyToClipboard, this, &ActivitySettings::slotCopyToClipboard);
|
||||
|
||||
_issuesWidget = new IssuesWidget(this);
|
||||
_syncIssueTabId = _tab->addTab(_issuesWidget, Theme::instance()->syncStateIcon(SyncResult::Problem), QString());
|
||||
slotShowIssueItemCount(0); // to display the label.
|
||||
connect(_issuesWidget, &IssuesWidget::issueCountUpdated,
|
||||
this, &ActivitySettings::slotShowIssueItemCount);
|
||||
connect(_issuesWidget, &IssuesWidget::copyToClipboard,
|
||||
this, &ActivitySettings::slotCopyToClipboard);
|
||||
|
||||
// Add a progress indicator to spin if the acitivity list is updated.
|
||||
_progressIndicator = new QProgressIndicator(this);
|
||||
_tab->setCornerWidget(_progressIndicator);
|
||||
|
||||
connect(&_notificationCheckTimer, &QTimer::timeout,
|
||||
this, &ActivitySettings::slotRegularNotificationCheck);
|
||||
|
||||
// connect a model signal to stop the animation.
|
||||
connect(_activityWidget, &ActivityWidget::rowsInserted, _progressIndicator, &QProgressIndicator::stopAnimation);
|
||||
// Add a progress indicator to spin if the acitivity list is updated.
|
||||
_progressIndicator = new QProgressIndicator(this);
|
||||
|
||||
// We want the protocol be the default
|
||||
_tab->setCurrentIndex(1);
|
||||
// connect a model signal to stop the animation
|
||||
connect(_activityWidget, &ActivityWidget::rowsInserted, _progressIndicator, &QProgressIndicator::stopAnimation);
|
||||
connect(_activityWidget, &ActivityWidget::rowsInserted, this, &ActivitySettings::slotDisplayActivities);
|
||||
}
|
||||
|
||||
void ActivitySettings::slotDisplayActivities(){
|
||||
_vbox->removeWidget(_progressIndicator);
|
||||
}
|
||||
|
||||
void ActivitySettings::setNotificationRefreshInterval(std::chrono::milliseconds interval)
|
||||
|
@ -539,118 +641,60 @@ void ActivitySettings::setNotificationRefreshInterval(std::chrono::milliseconds
|
|||
_notificationCheckTimer.start(interval.count());
|
||||
}
|
||||
|
||||
void ActivitySettings::setActivityTabHidden(bool hidden)
|
||||
{
|
||||
if (hidden && _activityTabId > -1) {
|
||||
_tab->removeTab(_activityTabId);
|
||||
_activityTabId = -1;
|
||||
_protocolTabId -= 1;
|
||||
_syncIssueTabId -= 1;
|
||||
}
|
||||
|
||||
if (!hidden && _activityTabId == -1) {
|
||||
_activityTabId = _tab->insertTab(0, _activityWidget, Theme::instance()->applicationIcon(), tr("Server Activity"));
|
||||
_protocolTabId += 1;
|
||||
_syncIssueTabId += 1;
|
||||
}
|
||||
}
|
||||
|
||||
void ActivitySettings::slotShowIssueItemCount(int cnt)
|
||||
{
|
||||
QString cntText = tr("Not Synced");
|
||||
if (cnt) {
|
||||
//: %1 is the number of not synced files.
|
||||
cntText = tr("Not Synced (%1)").arg(cnt);
|
||||
}
|
||||
_tab->setTabText(_syncIssueTabId, cntText);
|
||||
}
|
||||
|
||||
void ActivitySettings::slotShowActivityTab()
|
||||
{
|
||||
if (_activityTabId != -1) {
|
||||
_tab->setCurrentIndex(_activityTabId);
|
||||
}
|
||||
}
|
||||
|
||||
void ActivitySettings::slotShowIssuesTab(const QString &folderAlias)
|
||||
{
|
||||
if (_syncIssueTabId == -1)
|
||||
return;
|
||||
_tab->setCurrentIndex(_syncIssueTabId);
|
||||
|
||||
_issuesWidget->showFolderErrors(folderAlias);
|
||||
}
|
||||
|
||||
void ActivitySettings::slotCopyToClipboard()
|
||||
{
|
||||
QString text;
|
||||
QTextStream ts(&text);
|
||||
|
||||
int idx = _tab->currentIndex();
|
||||
QString message;
|
||||
|
||||
if (idx == _activityTabId) {
|
||||
// the activity widget
|
||||
_activityWidget->storeActivityList(ts);
|
||||
message = tr("The server activity list has been copied to the clipboard.");
|
||||
} else if (idx == _protocolTabId) {
|
||||
// the protocol widget
|
||||
_protocolWidget->storeSyncActivity(ts);
|
||||
message = tr("The sync activity list has been copied to the clipboard.");
|
||||
} else if (idx == _syncIssueTabId) {
|
||||
// issues Widget
|
||||
message = tr("The list of unsynced items has been copied to the clipboard.");
|
||||
_issuesWidget->storeSyncIssues(ts);
|
||||
}
|
||||
_activityWidget->storeActivityList(ts);
|
||||
message = tr("The server activity and notifications list has been copied to the clipboard.");
|
||||
|
||||
QApplication::clipboard()->setText(text);
|
||||
|
||||
emit guiLog(tr("Copied to clipboard"), message);
|
||||
}
|
||||
|
||||
void ActivitySettings::slotRemoveAccount(AccountState *ptr)
|
||||
void ActivitySettings::slotRemoveAccount()
|
||||
{
|
||||
_activityWidget->slotRemoveAccount(ptr);
|
||||
_activityWidget->slotRemoveAccount();
|
||||
}
|
||||
|
||||
void ActivitySettings::slotRefresh(AccountState *ptr)
|
||||
void ActivitySettings::slotRefresh()
|
||||
{
|
||||
// QElapsedTimer isn't actually constructed as invalid.
|
||||
if (!_timeSinceLastCheck.contains(ptr)) {
|
||||
_timeSinceLastCheck[ptr].invalidate();
|
||||
if (!_timeSinceLastCheck.contains(_accountState)) {
|
||||
_timeSinceLastCheck[_accountState].invalidate();
|
||||
}
|
||||
QElapsedTimer &timer = _timeSinceLastCheck[ptr];
|
||||
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 (ptr && ptr->isConnected()) {
|
||||
if (_accountState && _accountState->isConnected()) {
|
||||
if (isVisible() || !timer.isValid()) {
|
||||
_vbox->insertWidget(0, _progressIndicator);
|
||||
_vbox->setAlignment(_progressIndicator, Qt::AlignHCenter);
|
||||
_progressIndicator->startAnimation();
|
||||
_activityWidget->slotRefreshActivities(ptr);
|
||||
_activityWidget->slotRefreshActivities();
|
||||
}
|
||||
_activityWidget->slotRefreshNotifications(ptr);
|
||||
_activityWidget->slotRefreshNotifications();
|
||||
timer.start();
|
||||
}
|
||||
}
|
||||
|
||||
void ActivitySettings::slotRegularNotificationCheck()
|
||||
{
|
||||
AccountManager *am = AccountManager::instance();
|
||||
foreach (AccountStatePtr a, am->accounts()) {
|
||||
slotRefresh(a.data());
|
||||
}
|
||||
slotRefresh();
|
||||
}
|
||||
|
||||
bool ActivitySettings::event(QEvent *e)
|
||||
{
|
||||
if (e->type() == QEvent::Show) {
|
||||
AccountManager *am = AccountManager::instance();
|
||||
foreach (AccountStatePtr a, am->accounts()) {
|
||||
slotRefresh(a.data());
|
||||
}
|
||||
slotRefresh();
|
||||
}
|
||||
return QWidget::event(e);
|
||||
}
|
||||
|
|
|
@ -25,6 +25,7 @@
|
|||
#include "owncloudgui.h"
|
||||
#include "account.h"
|
||||
#include "activitydata.h"
|
||||
#include "accountmanager.h"
|
||||
|
||||
#include "ui_activitywidget.h"
|
||||
|
||||
|
@ -35,8 +36,6 @@ namespace OCC {
|
|||
|
||||
class Account;
|
||||
class AccountStatusPtr;
|
||||
class ProtocolWidget;
|
||||
class IssuesWidget;
|
||||
class JsonApiJob;
|
||||
class NotificationWidget;
|
||||
class ActivityListModel;
|
||||
|
@ -58,7 +57,7 @@ class ActivityWidget : public QWidget
|
|||
{
|
||||
Q_OBJECT
|
||||
public:
|
||||
explicit ActivityWidget(QWidget *parent = 0);
|
||||
explicit ActivityWidget(AccountState *accountState, QWidget *parent = 0);
|
||||
~ActivityWidget();
|
||||
QSize sizeHint() const Q_DECL_OVERRIDE { return ownCloudGui::settingsDialogSize(); }
|
||||
void storeActivityList(QTextStream &ts);
|
||||
|
@ -73,34 +72,38 @@ public:
|
|||
|
||||
public slots:
|
||||
void slotOpenFile(QModelIndex indx);
|
||||
void slotRefreshActivities(AccountState *ptr);
|
||||
void slotRefreshNotifications(AccountState *ptr);
|
||||
void slotRemoveAccount(AccountState *ptr);
|
||||
void slotAccountActivityStatus(AccountState *ast, int statusCode);
|
||||
void slotRefreshActivities();
|
||||
void slotRefreshNotifications();
|
||||
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);
|
||||
|
||||
signals:
|
||||
void guiLog(const QString &, const QString &);
|
||||
void copyToClipboard();
|
||||
void rowsInserted();
|
||||
void hideActivityTab(bool);
|
||||
void newNotification();
|
||||
void sendNotificationRequest(const QString &accountName, const QString &link, const QByteArray &verb, int row);
|
||||
|
||||
private slots:
|
||||
void slotBuildNotificationDisplay(const ActivityList &list);
|
||||
void slotSendNotificationRequest(const QString &accountName, const QString &link, const QByteArray &verb);
|
||||
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(NotificationWidget *widget, int replyCode);
|
||||
void scheduleWidgetToRemove(NotificationWidget *widget, int milliseconds = 100);
|
||||
void slotCheckToCleanWidgets();
|
||||
void slotNotificationRequestFinished(int statusCode);
|
||||
void slotPrimaryButtonClickedOnListView(const QModelIndex &index);
|
||||
void slotSecondaryButtonClickedOnListView(const QModelIndex &index);
|
||||
|
||||
private:
|
||||
void showLabels();
|
||||
QString timeString(QDateTime dt, QLocale::FormatType format) const;
|
||||
Ui::ActivityWidget *_ui;
|
||||
QPushButton *_copyBtn;
|
||||
|
||||
QSet<QString> _accountsWithoutActivities;
|
||||
QMap<Activity::Identifier, NotificationWidget *> _widgetForNotifId;
|
||||
QElapsedTimer _guiLogTimer;
|
||||
|
@ -115,7 +118,9 @@ private:
|
|||
int _notificationRequestsRunning;
|
||||
|
||||
ActivityListModel *_model;
|
||||
QVBoxLayout *_notificationsLayout;
|
||||
AccountState *_accountState;
|
||||
const QString _accept;
|
||||
const QString _remote_share;
|
||||
};
|
||||
|
||||
|
||||
|
@ -130,24 +135,20 @@ class ActivitySettings : public QWidget
|
|||
{
|
||||
Q_OBJECT
|
||||
public:
|
||||
explicit ActivitySettings(QWidget *parent = 0);
|
||||
explicit ActivitySettings(AccountState *accountState, QWidget *parent = 0);
|
||||
|
||||
~ActivitySettings();
|
||||
QSize sizeHint() const Q_DECL_OVERRIDE { return ownCloudGui::settingsDialogSize(); }
|
||||
|
||||
public slots:
|
||||
void slotRefresh(AccountState *ptr);
|
||||
void slotRemoveAccount(AccountState *ptr);
|
||||
|
||||
void slotRefresh();
|
||||
void slotRemoveAccount();
|
||||
void setNotificationRefreshInterval(std::chrono::milliseconds interval);
|
||||
|
||||
void slotShowIssuesTab(const QString &folderAlias);
|
||||
|
||||
private slots:
|
||||
void slotCopyToClipboard();
|
||||
void setActivityTabHidden(bool hidden);
|
||||
void slotRegularNotificationCheck();
|
||||
void slotShowIssueItemCount(int cnt);
|
||||
void slotShowActivityTab();
|
||||
void slotDisplayActivities();
|
||||
|
||||
signals:
|
||||
void guiLog(const QString &, const QString &);
|
||||
|
@ -155,17 +156,13 @@ signals:
|
|||
private:
|
||||
bool event(QEvent *e) Q_DECL_OVERRIDE;
|
||||
|
||||
QTabWidget *_tab;
|
||||
int _activityTabId;
|
||||
int _protocolTabId;
|
||||
int _syncIssueTabId;
|
||||
|
||||
ActivityWidget *_activityWidget;
|
||||
ProtocolWidget *_protocolWidget;
|
||||
IssuesWidget *_issuesWidget;
|
||||
QProgressIndicator *_progressIndicator;
|
||||
QVBoxLayout *_vbox;
|
||||
QTimer _notificationCheckTimer;
|
||||
QHash<AccountState *, QElapsedTimer> _timeSinceLastCheck;
|
||||
|
||||
AccountState *_accountState;
|
||||
};
|
||||
}
|
||||
#endif // ActivityWIDGET_H
|
||||
|
|
|
@ -10,69 +10,75 @@
|
|||
<height>556</height>
|
||||
</rect>
|
||||
</property>
|
||||
<property name="palette">
|
||||
<palette>
|
||||
<active>
|
||||
<colorrole role="Base">
|
||||
<brush brushstyle="SolidPattern">
|
||||
<color alpha="255">
|
||||
<red>252</red>
|
||||
<green>252</green>
|
||||
<blue>252</blue>
|
||||
</color>
|
||||
</brush>
|
||||
</colorrole>
|
||||
<colorrole role="Window">
|
||||
<brush brushstyle="SolidPattern">
|
||||
<color alpha="255">
|
||||
<red>255</red>
|
||||
<green>255</green>
|
||||
<blue>255</blue>
|
||||
</color>
|
||||
</brush>
|
||||
</colorrole>
|
||||
</active>
|
||||
<inactive>
|
||||
<colorrole role="Base">
|
||||
<brush brushstyle="SolidPattern">
|
||||
<color alpha="255">
|
||||
<red>252</red>
|
||||
<green>252</green>
|
||||
<blue>252</blue>
|
||||
</color>
|
||||
</brush>
|
||||
</colorrole>
|
||||
<colorrole role="Window">
|
||||
<brush brushstyle="SolidPattern">
|
||||
<color alpha="255">
|
||||
<red>255</red>
|
||||
<green>255</green>
|
||||
<blue>255</blue>
|
||||
</color>
|
||||
</brush>
|
||||
</colorrole>
|
||||
</inactive>
|
||||
<disabled>
|
||||
<colorrole role="Base">
|
||||
<brush brushstyle="SolidPattern">
|
||||
<color alpha="255">
|
||||
<red>255</red>
|
||||
<green>255</green>
|
||||
<blue>255</blue>
|
||||
</color>
|
||||
</brush>
|
||||
</colorrole>
|
||||
<colorrole role="Window">
|
||||
<brush brushstyle="SolidPattern">
|
||||
<color alpha="255">
|
||||
<red>255</red>
|
||||
<green>255</green>
|
||||
<blue>255</blue>
|
||||
</color>
|
||||
</brush>
|
||||
</colorrole>
|
||||
</disabled>
|
||||
</palette>
|
||||
</property>
|
||||
<property name="windowTitle">
|
||||
<string>Form</string>
|
||||
</property>
|
||||
<layout class="QGridLayout" name="gridLayout">
|
||||
<item row="0" column="0">
|
||||
<widget class="QLabel" name="_notifyLabel">
|
||||
<property name="sizePolicy">
|
||||
<sizepolicy hsizetype="Preferred" vsizetype="Maximum">
|
||||
<horstretch>0</horstretch>
|
||||
<verstretch>0</verstretch>
|
||||
</sizepolicy>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>TextLabel</string>
|
||||
</property>
|
||||
<property name="textFormat">
|
||||
<enum>Qt::PlainText</enum>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="1" column="0">
|
||||
<widget class="QScrollArea" name="_notifyScroll">
|
||||
<property name="sizePolicy">
|
||||
<sizepolicy hsizetype="Expanding" vsizetype="Preferred">
|
||||
<horstretch>0</horstretch>
|
||||
<verstretch>0</verstretch>
|
||||
</sizepolicy>
|
||||
</property>
|
||||
<property name="widgetResizable">
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
<property name="alignment">
|
||||
<set>Qt::AlignLeading|Qt::AlignLeft|Qt::AlignTop</set>
|
||||
</property>
|
||||
<widget class="QWidget" name="_scrollAreaWidgetContents">
|
||||
<property name="geometry">
|
||||
<rect>
|
||||
<x>0</x>
|
||||
<y>0</y>
|
||||
<width>677</width>
|
||||
<height>70</height>
|
||||
</rect>
|
||||
</property>
|
||||
</widget>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="2" column="0">
|
||||
<widget class="QLabel" name="_headerLabel">
|
||||
<property name="sizePolicy">
|
||||
<sizepolicy hsizetype="Preferred" vsizetype="Maximum">
|
||||
<horstretch>0</horstretch>
|
||||
<verstretch>0</verstretch>
|
||||
</sizepolicy>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>TextLabel</string>
|
||||
</property>
|
||||
<property name="textFormat">
|
||||
<enum>Qt::RichText</enum>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="3" column="0">
|
||||
<widget class="QListView" name="_activityList">
|
||||
<property name="sizePolicy">
|
||||
<sizepolicy hsizetype="Expanding" vsizetype="Expanding">
|
||||
|
@ -80,9 +86,106 @@
|
|||
<verstretch>0</verstretch>
|
||||
</sizepolicy>
|
||||
</property>
|
||||
<property name="palette">
|
||||
<palette>
|
||||
<active>
|
||||
<colorrole role="Base">
|
||||
<brush brushstyle="SolidPattern">
|
||||
<color alpha="255">
|
||||
<red>255</red>
|
||||
<green>255</green>
|
||||
<blue>255</blue>
|
||||
</color>
|
||||
</brush>
|
||||
</colorrole>
|
||||
<colorrole role="Window">
|
||||
<brush brushstyle="SolidPattern">
|
||||
<color alpha="255">
|
||||
<red>255</red>
|
||||
<green>255</green>
|
||||
<blue>255</blue>
|
||||
</color>
|
||||
</brush>
|
||||
</colorrole>
|
||||
</active>
|
||||
<inactive>
|
||||
<colorrole role="Base">
|
||||
<brush brushstyle="SolidPattern">
|
||||
<color alpha="255">
|
||||
<red>255</red>
|
||||
<green>255</green>
|
||||
<blue>255</blue>
|
||||
</color>
|
||||
</brush>
|
||||
</colorrole>
|
||||
<colorrole role="Window">
|
||||
<brush brushstyle="SolidPattern">
|
||||
<color alpha="255">
|
||||
<red>255</red>
|
||||
<green>255</green>
|
||||
<blue>255</blue>
|
||||
</color>
|
||||
</brush>
|
||||
</colorrole>
|
||||
</inactive>
|
||||
<disabled>
|
||||
<colorrole role="Base">
|
||||
<brush brushstyle="SolidPattern">
|
||||
<color alpha="255">
|
||||
<red>255</red>
|
||||
<green>255</green>
|
||||
<blue>255</blue>
|
||||
</color>
|
||||
</brush>
|
||||
</colorrole>
|
||||
<colorrole role="Window">
|
||||
<brush brushstyle="SolidPattern">
|
||||
<color alpha="255">
|
||||
<red>255</red>
|
||||
<green>255</green>
|
||||
<blue>255</blue>
|
||||
</color>
|
||||
</brush>
|
||||
</colorrole>
|
||||
</disabled>
|
||||
</palette>
|
||||
</property>
|
||||
<property name="frameShape">
|
||||
<enum>QFrame::NoFrame</enum>
|
||||
</property>
|
||||
<property name="frameShadow">
|
||||
<enum>QFrame::Plain</enum>
|
||||
</property>
|
||||
<property name="lineWidth">
|
||||
<number>0</number>
|
||||
</property>
|
||||
<property name="sizeAdjustPolicy">
|
||||
<enum>QAbstractScrollArea::AdjustToContents</enum>
|
||||
</property>
|
||||
<property name="editTriggers">
|
||||
<set>QAbstractItemView::NoEditTriggers</set>
|
||||
</property>
|
||||
<property name="showDropIndicator" stdset="0">
|
||||
<bool>false</bool>
|
||||
</property>
|
||||
<property name="defaultDropAction">
|
||||
<enum>Qt::IgnoreAction</enum>
|
||||
</property>
|
||||
<property name="resizeMode">
|
||||
<enum>QListView::Adjust</enum>
|
||||
</property>
|
||||
<property name="viewMode">
|
||||
<enum>QListView::ListMode</enum>
|
||||
</property>
|
||||
<property name="modelColumn">
|
||||
<number>0</number>
|
||||
</property>
|
||||
<property name="wordWrap">
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="4" column="0">
|
||||
<item row="1" column="0">
|
||||
<widget class="QLabel" name="_bottomLabel">
|
||||
<property name="sizePolicy">
|
||||
<sizepolicy hsizetype="Preferred" vsizetype="Maximum">
|
||||
|
@ -98,14 +201,32 @@
|
|||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="5" column="0">
|
||||
<widget class="QDialogButtonBox" name="_dialogButtonBox"/>
|
||||
<item row="3" column="0">
|
||||
<widget class="QPushButton" name="_copyButton">
|
||||
<property name="sizePolicy">
|
||||
<sizepolicy hsizetype="Fixed" vsizetype="Fixed">
|
||||
<horstretch>0</horstretch>
|
||||
<verstretch>0</verstretch>
|
||||
</sizepolicy>
|
||||
</property>
|
||||
<property name="minimumSize">
|
||||
<size>
|
||||
<width>120</width>
|
||||
<height>30</height>
|
||||
</size>
|
||||
</property>
|
||||
<property name="styleSheet">
|
||||
<string notr="true">margin-left:40px;</string>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>Copy</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
<tabstops>
|
||||
<tabstop>_activityList</tabstop>
|
||||
<tabstop>_dialogButtonBox</tabstop>
|
||||
</tabstops>
|
||||
<resources/>
|
||||
<connections/>
|
||||
|
|
37
src/gui/iconjob.cpp
Normal file
|
@ -0,0 +1,37 @@
|
|||
/*
|
||||
* Copyright (C) by Camila Ayres <hello@camila.codes>
|
||||
*
|
||||
* 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 "iconjob.h"
|
||||
|
||||
namespace OCC {
|
||||
|
||||
IconJob::IconJob(const QUrl &url, QObject *parent) :
|
||||
QObject(parent)
|
||||
{
|
||||
connect(&_accessManager, &QNetworkAccessManager::finished,
|
||||
this, &IconJob::finished);
|
||||
|
||||
QNetworkRequest request(url);
|
||||
_accessManager.get(request);
|
||||
}
|
||||
|
||||
void IconJob::finished(QNetworkReply *reply)
|
||||
{
|
||||
if (reply->error() != QNetworkReply::NoError)
|
||||
return;
|
||||
|
||||
reply->deleteLater();
|
||||
emit jobFinished(reply->readAll());
|
||||
}
|
||||
}
|
47
src/gui/iconjob.h
Normal file
|
@ -0,0 +1,47 @@
|
|||
/*
|
||||
* Copyright (C) by Camila Ayres <hello@camila.codes>
|
||||
*
|
||||
* 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 ICONJOB_H
|
||||
#define ICONJOB_H
|
||||
|
||||
#include <QObject>
|
||||
#include <QByteArray>
|
||||
#include <QNetworkAccessManager>
|
||||
#include <QNetworkRequest>
|
||||
#include <QNetworkReply>
|
||||
|
||||
namespace OCC {
|
||||
|
||||
/**
|
||||
* @brief Job to fetch a icon
|
||||
* @ingroup gui
|
||||
*/
|
||||
class IconJob : public QObject
|
||||
{
|
||||
Q_OBJECT
|
||||
public:
|
||||
explicit IconJob(const QUrl &url, QObject *parent = 0);
|
||||
|
||||
signals:
|
||||
void jobFinished(QByteArray iconData);
|
||||
|
||||
private slots:
|
||||
void finished(QNetworkReply *reply);
|
||||
|
||||
private:
|
||||
QNetworkAccessManager _accessManager;
|
||||
};
|
||||
}
|
||||
|
||||
#endif // ICONJOB_H
|
|
@ -1,540 +0,0 @@
|
|||
/*
|
||||
* Copyright (C) by Klaas Freitag <freitag@owncloud.com>
|
||||
*
|
||||
* 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 <QtGui>
|
||||
#include <QtWidgets>
|
||||
|
||||
#include "issueswidget.h"
|
||||
#include "configfile.h"
|
||||
#include "syncresult.h"
|
||||
#include "syncengine.h"
|
||||
#include "logger.h"
|
||||
#include "theme.h"
|
||||
#include "folderman.h"
|
||||
#include "syncfileitem.h"
|
||||
#include "folder.h"
|
||||
#include "openfilemanager.h"
|
||||
#include "activityitemdelegate.h"
|
||||
#include "protocolwidget.h"
|
||||
#include "accountstate.h"
|
||||
#include "account.h"
|
||||
#include "accountmanager.h"
|
||||
#include "common/syncjournalfilerecord.h"
|
||||
#include "elidedlabel.h"
|
||||
|
||||
|
||||
#include "ui_issueswidget.h"
|
||||
|
||||
#include <climits>
|
||||
|
||||
namespace OCC {
|
||||
|
||||
/**
|
||||
* If more issues are reported than this they will not show up
|
||||
* to avoid performance issues around sorting this many issues.
|
||||
*/
|
||||
static const int maxIssueCount = 50000;
|
||||
|
||||
static QPair<QString, QString> pathsWithIssuesKey(const ProtocolItem::ExtraData &data)
|
||||
{
|
||||
return qMakePair(data.folderName, data.path);
|
||||
}
|
||||
|
||||
IssuesWidget::IssuesWidget(QWidget *parent)
|
||||
: QWidget(parent)
|
||||
, _ui(new Ui::IssuesWidget)
|
||||
{
|
||||
_ui->setupUi(this);
|
||||
|
||||
connect(ProgressDispatcher::instance(), &ProgressDispatcher::progressInfo,
|
||||
this, &IssuesWidget::slotProgressInfo);
|
||||
connect(ProgressDispatcher::instance(), &ProgressDispatcher::itemCompleted,
|
||||
this, &IssuesWidget::slotItemCompleted);
|
||||
connect(ProgressDispatcher::instance(), &ProgressDispatcher::syncError,
|
||||
this, &IssuesWidget::addError);
|
||||
|
||||
connect(_ui->_treeWidget, &QTreeWidget::itemActivated, this, &IssuesWidget::slotOpenFile);
|
||||
connect(_ui->copyIssuesButton, &QAbstractButton::clicked, this, &IssuesWidget::copyToClipboard);
|
||||
|
||||
_ui->_treeWidget->setContextMenuPolicy(Qt::CustomContextMenu);
|
||||
connect(_ui->_treeWidget, &QTreeWidget::customContextMenuRequested, this, &IssuesWidget::slotItemContextMenu);
|
||||
|
||||
connect(_ui->showIgnores, &QAbstractButton::toggled, this, &IssuesWidget::slotRefreshIssues);
|
||||
connect(_ui->showWarnings, &QAbstractButton::toggled, this, &IssuesWidget::slotRefreshIssues);
|
||||
connect(_ui->filterAccount, static_cast<void (QComboBox::*)(int)>(&QComboBox::currentIndexChanged), this, &IssuesWidget::slotRefreshIssues);
|
||||
connect(_ui->filterAccount, static_cast<void (QComboBox::*)(int)>(&QComboBox::currentIndexChanged), this, &IssuesWidget::slotUpdateFolderFilters);
|
||||
connect(_ui->filterFolder, static_cast<void (QComboBox::*)(int)>(&QComboBox::currentIndexChanged), this, &IssuesWidget::slotRefreshIssues);
|
||||
for (auto account : AccountManager::instance()->accounts()) {
|
||||
slotAccountAdded(account.data());
|
||||
}
|
||||
connect(AccountManager::instance(), &AccountManager::accountAdded,
|
||||
this, &IssuesWidget::slotAccountAdded);
|
||||
connect(AccountManager::instance(), &AccountManager::accountRemoved,
|
||||
this, &IssuesWidget::slotAccountRemoved);
|
||||
connect(FolderMan::instance(), &FolderMan::folderListChanged,
|
||||
this, &IssuesWidget::slotUpdateFolderFilters);
|
||||
|
||||
|
||||
// Adjust copyToClipboard() when making changes here!
|
||||
QStringList header;
|
||||
header << tr("Time");
|
||||
header << tr("File");
|
||||
header << tr("Folder");
|
||||
header << tr("Issue");
|
||||
|
||||
int timestampColumnExtra = 0;
|
||||
#ifdef Q_OS_WIN
|
||||
timestampColumnExtra = 20; // font metrics are broken on Windows, see #4721
|
||||
#endif
|
||||
|
||||
_ui->_treeWidget->setHeaderLabels(header);
|
||||
int timestampColumnWidth =
|
||||
ActivityItemDelegate::rowHeight() // icon
|
||||
+ _ui->_treeWidget->fontMetrics().width(ProtocolItem::timeString(QDateTime::currentDateTime()))
|
||||
+ timestampColumnExtra;
|
||||
_ui->_treeWidget->setColumnWidth(0, timestampColumnWidth);
|
||||
_ui->_treeWidget->setColumnWidth(1, 180);
|
||||
_ui->_treeWidget->setColumnCount(4);
|
||||
_ui->_treeWidget->setRootIsDecorated(false);
|
||||
_ui->_treeWidget->setTextElideMode(Qt::ElideMiddle);
|
||||
_ui->_treeWidget->header()->setObjectName("ActivityErrorListHeader");
|
||||
#if defined(Q_OS_MAC)
|
||||
_ui->_treeWidget->setMinimumWidth(400);
|
||||
#endif
|
||||
|
||||
_reenableSorting.setInterval(5000);
|
||||
connect(&_reenableSorting, &QTimer::timeout, this,
|
||||
[this]() { _ui->_treeWidget->setSortingEnabled(true); });
|
||||
|
||||
_ui->_tooManyIssuesWarning->hide();
|
||||
connect(this, &IssuesWidget::issueCountUpdated, this,
|
||||
[this](int count) { _ui->_tooManyIssuesWarning->setVisible(count >= maxIssueCount); });
|
||||
|
||||
_ui->_conflictHelp->hide();
|
||||
_ui->_conflictHelp->setText(
|
||||
tr("There were conflicts. <a href=\"%1\">Check the documentation on how to resolve them.</a>")
|
||||
.arg(Theme::instance()->conflictHelpUrl()));
|
||||
}
|
||||
|
||||
IssuesWidget::~IssuesWidget()
|
||||
{
|
||||
delete _ui;
|
||||
}
|
||||
|
||||
void IssuesWidget::showEvent(QShowEvent *ev)
|
||||
{
|
||||
ConfigFile cfg;
|
||||
cfg.restoreGeometryHeader(_ui->_treeWidget->header());
|
||||
|
||||
// Sorting by section was newly enabled. But if we restore the header
|
||||
// from a state where sorting was disabled, both of these flags will be
|
||||
// false and sorting will be impossible!
|
||||
_ui->_treeWidget->header()->setSectionsClickable(true);
|
||||
_ui->_treeWidget->header()->setSortIndicatorShown(true);
|
||||
|
||||
// Switch back to "first important, then by time" ordering
|
||||
_ui->_treeWidget->sortByColumn(0, Qt::DescendingOrder);
|
||||
|
||||
QWidget::showEvent(ev);
|
||||
}
|
||||
|
||||
void IssuesWidget::hideEvent(QHideEvent *ev)
|
||||
{
|
||||
ConfigFile cfg;
|
||||
cfg.saveGeometryHeader(_ui->_treeWidget->header());
|
||||
QWidget::hideEvent(ev);
|
||||
}
|
||||
|
||||
static bool persistsUntilLocalDiscovery(QTreeWidgetItem *item)
|
||||
{
|
||||
const auto data = ProtocolItem::extraData(item);
|
||||
return data.status == SyncFileItem::Conflict
|
||||
|| (data.status == SyncFileItem::FileIgnored && data.direction == SyncFileItem::Up);
|
||||
}
|
||||
|
||||
void IssuesWidget::cleanItems(const std::function<bool(QTreeWidgetItem *)> &shouldDelete)
|
||||
{
|
||||
_ui->_treeWidget->setSortingEnabled(false);
|
||||
|
||||
// The issue list is a state, clear it and let the next sync fill it
|
||||
// with ignored files and propagation errors.
|
||||
int itemCnt = _ui->_treeWidget->topLevelItemCount();
|
||||
for (int cnt = itemCnt - 1; cnt >= 0; cnt--) {
|
||||
QTreeWidgetItem *item = _ui->_treeWidget->topLevelItem(cnt);
|
||||
if (shouldDelete(item)) {
|
||||
_pathsWithIssues.remove(pathsWithIssuesKey(ProtocolItem::extraData(item)));
|
||||
delete item;
|
||||
}
|
||||
}
|
||||
|
||||
_ui->_treeWidget->setSortingEnabled(true);
|
||||
|
||||
// update the tabtext
|
||||
emit(issueCountUpdated(_ui->_treeWidget->topLevelItemCount()));
|
||||
}
|
||||
|
||||
void IssuesWidget::addItem(QTreeWidgetItem *item)
|
||||
{
|
||||
if (!item)
|
||||
return;
|
||||
|
||||
int count = _ui->_treeWidget->topLevelItemCount();
|
||||
if (count >= maxIssueCount)
|
||||
return;
|
||||
|
||||
_ui->_treeWidget->setSortingEnabled(false);
|
||||
_reenableSorting.start();
|
||||
|
||||
// Insert item specific errors behind the others
|
||||
int insertLoc = 0;
|
||||
if (!item->text(1).isEmpty()) {
|
||||
for (int i = 0; i < count; ++i) {
|
||||
if (_ui->_treeWidget->topLevelItem(i)->text(1).isEmpty()) {
|
||||
insertLoc = i + 1;
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Wipe any existing message for the same folder and path
|
||||
auto newData = ProtocolItem::extraData(item);
|
||||
if (_pathsWithIssues.contains(pathsWithIssuesKey(newData))) {
|
||||
for (int i = 0; i < count; ++i) {
|
||||
auto otherItem = _ui->_treeWidget->topLevelItem(i);
|
||||
auto otherData = ProtocolItem::extraData(otherItem);
|
||||
if (otherData.path == newData.path && otherData.folderName == newData.folderName) {
|
||||
delete otherItem;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
_ui->_treeWidget->insertTopLevelItem(insertLoc, item);
|
||||
_pathsWithIssues.insert(pathsWithIssuesKey(newData));
|
||||
item->setHidden(!shouldBeVisible(item, currentAccountFilter(), currentFolderFilter()));
|
||||
emit issueCountUpdated(_ui->_treeWidget->topLevelItemCount());
|
||||
}
|
||||
|
||||
void IssuesWidget::slotOpenFile(QTreeWidgetItem *item, int)
|
||||
{
|
||||
QString fileName = item->text(1);
|
||||
if (Folder *folder = ProtocolItem::folder(item)) {
|
||||
// folder->path() always comes back with trailing path
|
||||
QString fullPath = folder->path() + fileName;
|
||||
if (QFile(fullPath).exists()) {
|
||||
showInFileManager(fullPath);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void IssuesWidget::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();
|
||||
cleanItems([&](QTreeWidgetItem *item) {
|
||||
if (ProtocolItem::extraData(item).folderName != folder)
|
||||
return false;
|
||||
if (style == LocalDiscoveryStyle::FilesystemOnly)
|
||||
return true;
|
||||
if (!persistsUntilLocalDiscovery(item))
|
||||
return true;
|
||||
|
||||
// Definitely wipe the entry if the file no longer exists
|
||||
if (!QFileInfo(f->path() + ProtocolItem::extraData(item).path).exists())
|
||||
return true;
|
||||
|
||||
auto path = QFileInfo(ProtocolItem::extraData(item).path).dir().path().toUtf8();
|
||||
if (path == ".")
|
||||
path.clear();
|
||||
|
||||
return engine.shouldDiscoverLocally(path);
|
||||
});
|
||||
}
|
||||
if (progress.status() == ProgressInfo::Done) {
|
||||
// We keep track very well of pending conflicts.
|
||||
// Inform other components about them.
|
||||
QStringList conflicts;
|
||||
auto tree = _ui->_treeWidget;
|
||||
for (int i = 0; i < tree->topLevelItemCount(); ++i) {
|
||||
auto item = tree->topLevelItem(i);
|
||||
auto data = ProtocolItem::extraData(item);
|
||||
if (data.folderName == folder
|
||||
&& data.status == SyncFileItem::Conflict) {
|
||||
conflicts.append(data.path);
|
||||
}
|
||||
}
|
||||
emit ProgressDispatcher::instance()->folderConflicts(folder, conflicts);
|
||||
|
||||
_ui->_conflictHelp->setHidden(Theme::instance()->conflictHelpUrl().isEmpty() || conflicts.isEmpty());
|
||||
}
|
||||
}
|
||||
|
||||
void IssuesWidget::slotItemCompleted(const QString &folder, const SyncFileItemPtr &item)
|
||||
{
|
||||
if (!item->showInIssuesTab())
|
||||
return;
|
||||
QTreeWidgetItem *line = ProtocolItem::create(folder, *item);
|
||||
if (!line)
|
||||
return;
|
||||
addItem(line);
|
||||
}
|
||||
|
||||
void IssuesWidget::slotRefreshIssues()
|
||||
{
|
||||
auto tree = _ui->_treeWidget;
|
||||
auto filterFolderAlias = currentFolderFilter();
|
||||
auto filterAccount = currentAccountFilter();
|
||||
|
||||
for (int i = 0; i < tree->topLevelItemCount(); ++i) {
|
||||
auto item = tree->topLevelItem(i);
|
||||
item->setHidden(!shouldBeVisible(item, filterAccount, filterFolderAlias));
|
||||
}
|
||||
|
||||
_ui->_treeWidget->setColumnHidden(2, !filterFolderAlias.isEmpty());
|
||||
}
|
||||
|
||||
void IssuesWidget::slotAccountAdded(AccountState *account)
|
||||
{
|
||||
_ui->filterAccount->addItem(account->account()->displayName(), QVariant::fromValue(account));
|
||||
updateAccountChoiceVisibility();
|
||||
}
|
||||
|
||||
void IssuesWidget::slotAccountRemoved(AccountState *account)
|
||||
{
|
||||
for (int i = _ui->filterAccount->count() - 1; i >= 0; --i) {
|
||||
if (account == _ui->filterAccount->itemData(i).value<AccountState *>())
|
||||
_ui->filterAccount->removeItem(i);
|
||||
}
|
||||
updateAccountChoiceVisibility();
|
||||
}
|
||||
|
||||
void IssuesWidget::slotItemContextMenu(const QPoint &pos)
|
||||
{
|
||||
auto item = _ui->_treeWidget->itemAt(pos);
|
||||
if (!item)
|
||||
return;
|
||||
auto globalPos = _ui->_treeWidget->viewport()->mapToGlobal(pos);
|
||||
ProtocolItem::openContextMenu(globalPos, item, this);
|
||||
}
|
||||
|
||||
void IssuesWidget::updateAccountChoiceVisibility()
|
||||
{
|
||||
bool visible = _ui->filterAccount->count() > 2;
|
||||
_ui->filterAccount->setVisible(visible);
|
||||
_ui->accountLabel->setVisible(visible);
|
||||
slotUpdateFolderFilters();
|
||||
}
|
||||
|
||||
AccountState *IssuesWidget::currentAccountFilter() const
|
||||
{
|
||||
return _ui->filterAccount->currentData().value<AccountState *>();
|
||||
}
|
||||
|
||||
QString IssuesWidget::currentFolderFilter() const
|
||||
{
|
||||
return _ui->filterFolder->currentData().toString();
|
||||
}
|
||||
|
||||
bool IssuesWidget::shouldBeVisible(QTreeWidgetItem *item, AccountState *filterAccount,
|
||||
const QString &filterFolderAlias) const
|
||||
{
|
||||
bool visible = true;
|
||||
auto data = ProtocolItem::extraData(item);
|
||||
auto status = data.status;
|
||||
visible &= (_ui->showIgnores->isChecked() || status != SyncFileItem::FileIgnored);
|
||||
visible &= (_ui->showWarnings->isChecked()
|
||||
|| (status != SyncFileItem::SoftError
|
||||
&& status != SyncFileItem::Restoration));
|
||||
|
||||
const auto &folderalias = data.folderName;
|
||||
if (filterAccount) {
|
||||
auto folder = FolderMan::instance()->folder(folderalias);
|
||||
visible &= folder && folder->accountState() == filterAccount;
|
||||
}
|
||||
visible &= (filterFolderAlias.isEmpty() || filterFolderAlias == folderalias);
|
||||
|
||||
return visible;
|
||||
}
|
||||
|
||||
void IssuesWidget::slotUpdateFolderFilters()
|
||||
{
|
||||
auto account = _ui->filterAccount->currentData().value<AccountState *>();
|
||||
|
||||
// If there is no account selector, show folders for the single
|
||||
// available account
|
||||
if (_ui->filterAccount->isHidden() && _ui->filterAccount->count() > 1) {
|
||||
account = _ui->filterAccount->itemData(1).value<AccountState *>();
|
||||
}
|
||||
|
||||
if (!account) {
|
||||
_ui->filterFolder->setCurrentIndex(0);
|
||||
}
|
||||
_ui->filterFolder->setEnabled(account != 0);
|
||||
|
||||
for (int i = _ui->filterFolder->count() - 1; i >= 1; --i) {
|
||||
_ui->filterFolder->removeItem(i);
|
||||
}
|
||||
|
||||
// Find all selectable folders while figuring out if we need a folder
|
||||
// selector in the first place
|
||||
bool anyAccountHasMultipleFolders = false;
|
||||
QSet<AccountState *> accountsWithFolders;
|
||||
for (auto folder : FolderMan::instance()->map().values()) {
|
||||
if (accountsWithFolders.contains(folder->accountState()))
|
||||
anyAccountHasMultipleFolders = true;
|
||||
accountsWithFolders.insert(folder->accountState());
|
||||
|
||||
if (folder->accountState() != account)
|
||||
continue;
|
||||
_ui->filterFolder->addItem(folder->shortGuiLocalPath(), folder->alias());
|
||||
}
|
||||
|
||||
// If we don't need the combo box, hide it.
|
||||
_ui->filterFolder->setVisible(anyAccountHasMultipleFolders);
|
||||
_ui->folderLabel->setVisible(anyAccountHasMultipleFolders);
|
||||
|
||||
// If there's no choice, select the only folder and disable
|
||||
if (_ui->filterFolder->count() == 2 && anyAccountHasMultipleFolders) {
|
||||
_ui->filterFolder->setCurrentIndex(1);
|
||||
_ui->filterFolder->setEnabled(false);
|
||||
}
|
||||
}
|
||||
|
||||
void IssuesWidget::storeSyncIssues(QTextStream &ts)
|
||||
{
|
||||
int topLevelItems = _ui->_treeWidget->topLevelItemCount();
|
||||
|
||||
for (int i = 0; i < topLevelItems; i++) {
|
||||
QTreeWidgetItem *child = _ui->_treeWidget->topLevelItem(i);
|
||||
if (child->isHidden())
|
||||
continue;
|
||||
ts << right
|
||||
// time stamp
|
||||
<< qSetFieldWidth(20)
|
||||
<< child->data(0, Qt::DisplayRole).toString()
|
||||
// separator
|
||||
<< qSetFieldWidth(0) << ","
|
||||
|
||||
// file name
|
||||
<< qSetFieldWidth(64)
|
||||
<< child->data(1, Qt::DisplayRole).toString()
|
||||
// separator
|
||||
<< qSetFieldWidth(0) << ","
|
||||
|
||||
// folder
|
||||
<< qSetFieldWidth(30)
|
||||
<< child->data(2, Qt::DisplayRole).toString()
|
||||
// separator
|
||||
<< qSetFieldWidth(0) << ","
|
||||
|
||||
// action
|
||||
<< qSetFieldWidth(15)
|
||||
<< child->data(3, Qt::DisplayRole).toString()
|
||||
<< qSetFieldWidth(0)
|
||||
<< endl;
|
||||
}
|
||||
}
|
||||
|
||||
void IssuesWidget::showFolderErrors(const QString &folderAlias)
|
||||
{
|
||||
auto folder = FolderMan::instance()->folder(folderAlias);
|
||||
if (!folder)
|
||||
return;
|
||||
|
||||
_ui->filterAccount->setCurrentIndex(
|
||||
qMax(0, _ui->filterAccount->findData(QVariant::fromValue(folder->accountState()))));
|
||||
_ui->filterFolder->setCurrentIndex(
|
||||
qMax(0, _ui->filterFolder->findData(folderAlias)));
|
||||
_ui->showIgnores->setChecked(false);
|
||||
_ui->showWarnings->setChecked(false);
|
||||
}
|
||||
|
||||
void IssuesWidget::addError(const QString &folderAlias, const QString &message,
|
||||
ErrorCategory category)
|
||||
{
|
||||
auto folder = FolderMan::instance()->folder(folderAlias);
|
||||
if (!folder)
|
||||
return;
|
||||
|
||||
QStringList columns;
|
||||
QDateTime timestamp = QDateTime::currentDateTime();
|
||||
const QString timeStr = ProtocolItem::timeString(timestamp);
|
||||
const QString longTimeStr = ProtocolItem::timeString(timestamp, QLocale::LongFormat);
|
||||
|
||||
columns << timeStr;
|
||||
columns << ""; // no "File" entry
|
||||
columns << folder->shortGuiLocalPath();
|
||||
columns << message;
|
||||
|
||||
QIcon icon = Theme::instance()->syncStateIcon(SyncResult::Error);
|
||||
|
||||
QTreeWidgetItem *twitem = new ProtocolItem(columns);
|
||||
twitem->setData(0, Qt::SizeHintRole, QSize(0, ActivityItemDelegate::rowHeight()));
|
||||
twitem->setIcon(0, icon);
|
||||
twitem->setToolTip(0, longTimeStr);
|
||||
twitem->setToolTip(3, message);
|
||||
ProtocolItem::ExtraData data;
|
||||
data.timestamp = timestamp;
|
||||
data.folderName = folderAlias;
|
||||
data.status = SyncFileItem::NormalError;
|
||||
ProtocolItem::setExtraData(twitem, data);
|
||||
|
||||
addItem(twitem);
|
||||
addErrorWidget(twitem, message, category);
|
||||
}
|
||||
|
||||
void IssuesWidget::addErrorWidget(QTreeWidgetItem *item, const QString &message, ErrorCategory category)
|
||||
{
|
||||
QWidget *widget = 0;
|
||||
if (category == ErrorCategory::InsufficientRemoteStorage) {
|
||||
widget = new QWidget;
|
||||
auto layout = new QHBoxLayout;
|
||||
widget->setLayout(layout);
|
||||
|
||||
auto label = new ElidedLabel(message, widget);
|
||||
label->setElideMode(Qt::ElideMiddle);
|
||||
layout->addWidget(label);
|
||||
|
||||
auto button = new QPushButton("Retry all uploads", widget);
|
||||
button->setSizePolicy(QSizePolicy::Maximum, QSizePolicy::Expanding);
|
||||
auto folderAlias = ProtocolItem::extraData(item).folderName;
|
||||
connect(button, &QPushButton::clicked,
|
||||
this, [this, folderAlias]() { retryInsufficentRemoteStorageErrors(folderAlias); });
|
||||
layout->addWidget(button);
|
||||
}
|
||||
|
||||
if (widget) {
|
||||
item->setText(3, QString());
|
||||
}
|
||||
_ui->_treeWidget->setItemWidget(item, 3, widget);
|
||||
}
|
||||
|
||||
void IssuesWidget::retryInsufficentRemoteStorageErrors(const QString &folderAlias)
|
||||
{
|
||||
auto folderman = FolderMan::instance();
|
||||
auto folder = folderman->folder(folderAlias);
|
||||
if (!folder)
|
||||
return;
|
||||
|
||||
folder->journalDb()->wipeErrorBlacklistCategory(SyncJournalErrorBlacklistRecord::InsufficientRemoteStorage);
|
||||
folderman->scheduleFolderNext(folder);
|
||||
}
|
||||
}
|
|
@ -1,99 +0,0 @@
|
|||
/*
|
||||
* Copyright (C) by Klaas Freitag <freitag@owncloud.com>
|
||||
*
|
||||
* 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 ISSUESWIDGET_H
|
||||
#define ISSUESWIDGET_H
|
||||
|
||||
#include <QDialog>
|
||||
#include <QDateTime>
|
||||
#include <QLocale>
|
||||
#include <QTimer>
|
||||
|
||||
#include "progressdispatcher.h"
|
||||
#include "owncloudgui.h"
|
||||
|
||||
#include "ui_issueswidget.h"
|
||||
|
||||
class QPushButton;
|
||||
|
||||
namespace OCC {
|
||||
class SyncResult;
|
||||
|
||||
namespace Ui {
|
||||
class ProtocolWidget;
|
||||
}
|
||||
class Application;
|
||||
|
||||
/**
|
||||
* @brief The ProtocolWidget class
|
||||
* @ingroup gui
|
||||
*/
|
||||
class IssuesWidget : public QWidget
|
||||
{
|
||||
Q_OBJECT
|
||||
public:
|
||||
explicit IssuesWidget(QWidget *parent = 0);
|
||||
~IssuesWidget();
|
||||
QSize sizeHint() const { return ownCloudGui::settingsDialogSize(); }
|
||||
|
||||
void storeSyncIssues(QTextStream &ts);
|
||||
void showFolderErrors(const QString &folderAlias);
|
||||
|
||||
public slots:
|
||||
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);
|
||||
|
||||
protected:
|
||||
void showEvent(QShowEvent *);
|
||||
void hideEvent(QHideEvent *);
|
||||
|
||||
signals:
|
||||
void copyToClipboard();
|
||||
void issueCountUpdated(int);
|
||||
|
||||
private slots:
|
||||
void slotRefreshIssues();
|
||||
void slotUpdateFolderFilters();
|
||||
void slotAccountAdded(AccountState *account);
|
||||
void slotAccountRemoved(AccountState *account);
|
||||
void slotItemContextMenu(const QPoint &pos);
|
||||
|
||||
private:
|
||||
void updateAccountChoiceVisibility();
|
||||
AccountState *currentAccountFilter() const;
|
||||
QString currentFolderFilter() const;
|
||||
bool shouldBeVisible(QTreeWidgetItem *item, AccountState *filterAccount,
|
||||
const QString &filterFolderAlias) const;
|
||||
void cleanItems(const std::function<bool(QTreeWidgetItem *)> &shouldDelete);
|
||||
void addItem(QTreeWidgetItem *item);
|
||||
|
||||
/// Add the special error widget for the category, if any
|
||||
void addErrorWidget(QTreeWidgetItem *item, const QString &message, ErrorCategory category);
|
||||
|
||||
/// Wipes all insufficient remote storgage blacklist entries
|
||||
void retryInsufficentRemoteStorageErrors(const QString &folderAlias);
|
||||
|
||||
/// Each insert disables sorting, this timer reenables it
|
||||
QTimer _reenableSorting;
|
||||
|
||||
/// Optimization: keep track of all folder/paths pairs that have an associated issue
|
||||
QSet<QPair<QString, QString>> _pathsWithIssues;
|
||||
|
||||
Ui::IssuesWidget *_ui;
|
||||
};
|
||||
}
|
||||
|
||||
#endif
|
|
@ -1,198 +0,0 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<ui version="4.0">
|
||||
<class>OCC::IssuesWidget</class>
|
||||
<widget class="QWidget" name="OCC::IssuesWidget">
|
||||
<property name="geometry">
|
||||
<rect>
|
||||
<x>0</x>
|
||||
<y>0</y>
|
||||
<width>580</width>
|
||||
<height>578</height>
|
||||
</rect>
|
||||
</property>
|
||||
<property name="windowTitle">
|
||||
<string>Form</string>
|
||||
</property>
|
||||
<layout class="QVBoxLayout" name="verticalLayout">
|
||||
<item>
|
||||
<widget class="QLabel" name="_headerLabel">
|
||||
<property name="text">
|
||||
<string>List of issues</string>
|
||||
</property>
|
||||
<property name="textFormat">
|
||||
<enum>Qt::PlainText</enum>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<layout class="QGridLayout" name="gridLayout">
|
||||
<item row="0" column="0">
|
||||
<layout class="QFormLayout" name="accountFolderLayout">
|
||||
<item row="0" column="0">
|
||||
<widget class="QLabel" name="accountLabel">
|
||||
<property name="text">
|
||||
<string>Account</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="0" column="1">
|
||||
<widget class="QComboBox" name="filterAccount">
|
||||
<item>
|
||||
<property name="text">
|
||||
<string><no filter></string>
|
||||
</property>
|
||||
</item>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="1" column="0">
|
||||
<widget class="QLabel" name="folderLabel">
|
||||
<property name="text">
|
||||
<string>Folder</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="1" column="1">
|
||||
<widget class="QComboBox" name="filterFolder">
|
||||
<property name="enabled">
|
||||
<bool>false</bool>
|
||||
</property>
|
||||
<item>
|
||||
<property name="text">
|
||||
<string><no filter></string>
|
||||
</property>
|
||||
</item>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</item>
|
||||
<item row="0" column="1">
|
||||
<layout class="QFormLayout" name="formLayout_2">
|
||||
<item row="0" column="1">
|
||||
<widget class="QCheckBox" name="showWarnings">
|
||||
<property name="text">
|
||||
<string>Show warnings</string>
|
||||
</property>
|
||||
<property name="checked">
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="1" column="1">
|
||||
<widget class="QCheckBox" name="showIgnores">
|
||||
<property name="text">
|
||||
<string>Show ignored files</string>
|
||||
</property>
|
||||
<property name="checked">
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</item>
|
||||
</layout>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QTreeWidget" name="_treeWidget">
|
||||
<property name="alternatingRowColors">
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
<property name="rootIsDecorated">
|
||||
<bool>false</bool>
|
||||
</property>
|
||||
<property name="sortingEnabled">
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
<property name="columnCount">
|
||||
<number>4</number>
|
||||
</property>
|
||||
<column>
|
||||
<property name="text">
|
||||
<string notr="true">1</string>
|
||||
</property>
|
||||
</column>
|
||||
<column>
|
||||
<property name="text">
|
||||
<string notr="true">2</string>
|
||||
</property>
|
||||
</column>
|
||||
<column>
|
||||
<property name="text">
|
||||
<string notr="true">3</string>
|
||||
</property>
|
||||
</column>
|
||||
<column>
|
||||
<property name="text">
|
||||
<string notr="true">4</string>
|
||||
</property>
|
||||
</column>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<layout class="QHBoxLayout" name="horizontalLayout_2" stretch="1,0">
|
||||
<item>
|
||||
<layout class="QVBoxLayout" name="verticalLayout_2">
|
||||
<item>
|
||||
<widget class="QLabel" name="_tooManyIssuesWarning">
|
||||
<property name="text">
|
||||
<string>There were too many issues. Not all will be visible here.</string>
|
||||
</property>
|
||||
<property name="wordWrap">
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QLabel" name="_conflictHelp">
|
||||
<property name="text">
|
||||
<string>There were conflicts. Check the documentation on how to resolve them.</string>
|
||||
</property>
|
||||
<property name="wordWrap">
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
<property name="openExternalLinks">
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</item>
|
||||
<item>
|
||||
<layout class="QVBoxLayout" name="verticalLayout_3">
|
||||
<item>
|
||||
<spacer name="verticalSpacer">
|
||||
<property name="orientation">
|
||||
<enum>Qt::Vertical</enum>
|
||||
</property>
|
||||
<property name="sizeType">
|
||||
<enum>QSizePolicy::Minimum</enum>
|
||||
</property>
|
||||
<property name="sizeHint" stdset="0">
|
||||
<size>
|
||||
<width>20</width>
|
||||
<height>0</height>
|
||||
</size>
|
||||
</property>
|
||||
</spacer>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QPushButton" name="copyIssuesButton">
|
||||
<property name="toolTip">
|
||||
<string>Copy the issues list to the clipboard.</string>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>Copy</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</item>
|
||||
</layout>
|
||||
</item>
|
||||
<item>
|
||||
<layout class="QHBoxLayout" name="horizontalLayout"/>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
<resources/>
|
||||
<connections/>
|
||||
</ui>
|
|
@ -47,7 +47,7 @@ void NotificationWidget::setActivity(const Activity &activity)
|
|||
_ui._subjectLabel->setText(activity._subject);
|
||||
_ui._messageLabel->setText(activity._message);
|
||||
|
||||
_ui._notifIcon->setPixmap(QPixmap(":/client/resources/bell.png"));
|
||||
_ui._notifIcon->setPixmap(QPixmap(":/client/resources/bell.svg"));
|
||||
_ui._notifIcon->setMinimumWidth(22);
|
||||
_ui._notifIcon->setMinimumHeight(22);
|
||||
_ui._notifIcon->show();
|
||||
|
|
|
@ -7,7 +7,7 @@
|
|||
<x>0</x>
|
||||
<y>0</y>
|
||||
<width>725</width>
|
||||
<height>125</height>
|
||||
<height>129</height>
|
||||
</rect>
|
||||
</property>
|
||||
<property name="sizePolicy">
|
||||
|
@ -75,7 +75,7 @@
|
|||
<string/>
|
||||
</property>
|
||||
<property name="pixmap">
|
||||
<pixmap>../../../../resources/bell.png</pixmap>
|
||||
<pixmap>../../resources/bell.svg</pixmap>
|
||||
</property>
|
||||
<property name="alignment">
|
||||
<set>Qt::AlignBottom|Qt::AlignLeading|Qt::AlignLeft</set>
|
||||
|
|
|
@ -1040,7 +1040,7 @@ void ownCloudGui::slotShowSettings()
|
|||
void ownCloudGui::slotShowSyncProtocol()
|
||||
{
|
||||
slotShowSettings();
|
||||
_settingsDialog->showActivityPage();
|
||||
//_settingsDialog->showActivityPage();
|
||||
}
|
||||
|
||||
|
||||
|
|
|
@ -1,330 +0,0 @@
|
|||
/*
|
||||
* Copyright (C) by Klaas Freitag <freitag@owncloud.com>
|
||||
*
|
||||
* 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 <QtGui>
|
||||
#include <QtWidgets>
|
||||
|
||||
#include "protocolwidget.h"
|
||||
#include "configfile.h"
|
||||
#include "syncresult.h"
|
||||
#include "logger.h"
|
||||
#include "theme.h"
|
||||
#include "folderman.h"
|
||||
#include "syncfileitem.h"
|
||||
#include "folder.h"
|
||||
#include "openfilemanager.h"
|
||||
#include "activityitemdelegate.h"
|
||||
#include "guiutility.h"
|
||||
#include "accountstate.h"
|
||||
|
||||
#include "ui_protocolwidget.h"
|
||||
|
||||
#include <climits>
|
||||
|
||||
Q_DECLARE_METATYPE(OCC::ProtocolItem::ExtraData)
|
||||
|
||||
namespace OCC {
|
||||
|
||||
QString ProtocolItem::timeString(QDateTime dt, QLocale::FormatType format)
|
||||
{
|
||||
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);
|
||||
}
|
||||
|
||||
ProtocolItem::ExtraData ProtocolItem::extraData(const QTreeWidgetItem *item)
|
||||
{
|
||||
return item->data(0, Qt::UserRole).value<ExtraData>();
|
||||
}
|
||||
|
||||
void ProtocolItem::setExtraData(QTreeWidgetItem *item, const ExtraData &data)
|
||||
{
|
||||
item->setData(0, Qt::UserRole, QVariant::fromValue(data));
|
||||
}
|
||||
|
||||
ProtocolItem *ProtocolItem::create(const QString &folder, const SyncFileItem &item)
|
||||
{
|
||||
auto f = FolderMan::instance()->folder(folder);
|
||||
if (!f) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
QStringList columns;
|
||||
QDateTime timestamp = QDateTime::currentDateTime();
|
||||
const QString timeStr = timeString(timestamp);
|
||||
const QString longTimeStr = timeString(timestamp, QLocale::LongFormat);
|
||||
|
||||
columns << timeStr;
|
||||
columns << Utility::fileNameForGuiUse(item._originalFile);
|
||||
columns << f->shortGuiLocalPath();
|
||||
|
||||
// If the error string is set, it's prefered because it is a useful user message.
|
||||
QString message = item._errorString;
|
||||
if (message.isEmpty()) {
|
||||
message = Progress::asResultString(item);
|
||||
}
|
||||
columns << message;
|
||||
|
||||
QIcon icon;
|
||||
if (item._status == SyncFileItem::NormalError
|
||||
|| item._status == SyncFileItem::FatalError
|
||||
|| item._status == SyncFileItem::DetailError
|
||||
|| item._status == SyncFileItem::BlacklistedError) {
|
||||
icon = Theme::instance()->syncStateIcon(SyncResult::Error);
|
||||
} else if (Progress::isWarningKind(item._status)) {
|
||||
icon = Theme::instance()->syncStateIcon(SyncResult::Problem);
|
||||
}
|
||||
|
||||
if (ProgressInfo::isSizeDependent(item)) {
|
||||
columns << Utility::octetsToString(item._size);
|
||||
}
|
||||
|
||||
ProtocolItem *twitem = new ProtocolItem(columns);
|
||||
// Warning: The data and tooltips on the columns define an implicit
|
||||
// interface and can only be changed with care.
|
||||
twitem->setData(0, Qt::SizeHintRole, QSize(0, ActivityItemDelegate::rowHeight()));
|
||||
twitem->setIcon(0, icon);
|
||||
twitem->setToolTip(0, longTimeStr);
|
||||
twitem->setToolTip(1, item._file);
|
||||
twitem->setToolTip(3, message);
|
||||
ProtocolItem::ExtraData data;
|
||||
data.timestamp = timestamp;
|
||||
data.path = item._file;
|
||||
data.folderName = folder;
|
||||
data.status = item._status;
|
||||
data.size = item._size;
|
||||
data.direction = item._direction;
|
||||
ProtocolItem::setExtraData(twitem, data);
|
||||
return twitem;
|
||||
}
|
||||
|
||||
SyncJournalFileRecord ProtocolItem::syncJournalRecord(QTreeWidgetItem *item)
|
||||
{
|
||||
SyncJournalFileRecord rec;
|
||||
auto f = folder(item);
|
||||
if (!f)
|
||||
return rec;
|
||||
f->journalDb()->getFileRecord(extraData(item).path, &rec);
|
||||
return rec;
|
||||
}
|
||||
|
||||
Folder *ProtocolItem::folder(QTreeWidgetItem *item)
|
||||
{
|
||||
return FolderMan::instance()->folder(extraData(item).folderName);
|
||||
}
|
||||
|
||||
void ProtocolItem::openContextMenu(QPoint globalPos, QTreeWidgetItem *item, QWidget *parent)
|
||||
{
|
||||
auto f = folder(item);
|
||||
if (!f)
|
||||
return;
|
||||
AccountPtr account = f->accountState()->account();
|
||||
auto rec = syncJournalRecord(item);
|
||||
// rec might not be valid
|
||||
|
||||
auto menu = new QMenu(parent);
|
||||
|
||||
if (rec.isValid()) {
|
||||
// "Open in Browser" action
|
||||
auto openInBrowser = menu->addAction(ProtocolWidget::tr("Open in browser"));
|
||||
QObject::connect(openInBrowser, &QAction::triggered, parent, [parent, account, rec]() {
|
||||
fetchPrivateLinkUrl(account, rec._path, rec.numericFileId(), parent,
|
||||
[parent](const QString &url) {
|
||||
Utility::openBrowser(url, parent);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
// More actions will be conditionally added to the context menu here later
|
||||
|
||||
if (menu->actions().isEmpty()) {
|
||||
delete menu;
|
||||
return;
|
||||
}
|
||||
|
||||
menu->setAttribute(Qt::WA_DeleteOnClose);
|
||||
menu->popup(globalPos);
|
||||
}
|
||||
|
||||
bool ProtocolItem::operator<(const QTreeWidgetItem &other) const
|
||||
{
|
||||
int column = treeWidget()->sortColumn();
|
||||
if (column == 0) {
|
||||
// Items with empty "File" column are larger than others,
|
||||
// otherwise sort by time (this uses lexicographic ordering)
|
||||
return std::forward_as_tuple(text(1).isEmpty(), extraData(this).timestamp)
|
||||
< std::forward_as_tuple(other.text(1).isEmpty(), extraData(&other).timestamp);
|
||||
} else if (column == 4) {
|
||||
return extraData(this).size < extraData(&other).size;
|
||||
}
|
||||
|
||||
return QTreeWidgetItem::operator<(other);
|
||||
}
|
||||
|
||||
ProtocolWidget::ProtocolWidget(QWidget *parent)
|
||||
: QWidget(parent)
|
||||
, _ui(new Ui::ProtocolWidget)
|
||||
{
|
||||
_ui->setupUi(this);
|
||||
|
||||
connect(ProgressDispatcher::instance(), &ProgressDispatcher::itemCompleted,
|
||||
this, &ProtocolWidget::slotItemCompleted);
|
||||
|
||||
connect(_ui->_treeWidget, &QTreeWidget::itemActivated, this, &ProtocolWidget::slotOpenFile);
|
||||
|
||||
_ui->_treeWidget->setContextMenuPolicy(Qt::CustomContextMenu);
|
||||
connect(_ui->_treeWidget, &QTreeWidget::customContextMenuRequested, this, &ProtocolWidget::slotItemContextMenu);
|
||||
|
||||
// Adjust copyToClipboard() when making changes here!
|
||||
QStringList header;
|
||||
header << tr("Time");
|
||||
header << tr("File");
|
||||
header << tr("Folder");
|
||||
header << tr("Action");
|
||||
header << tr("Size");
|
||||
|
||||
int timestampColumnExtra = 0;
|
||||
#ifdef Q_OS_WIN
|
||||
timestampColumnExtra = 20; // font metrics are broken on Windows, see #4721
|
||||
#endif
|
||||
|
||||
_ui->_treeWidget->setHeaderLabels(header);
|
||||
int timestampColumnWidth =
|
||||
_ui->_treeWidget->fontMetrics().width(ProtocolItem::timeString(QDateTime::currentDateTime()))
|
||||
+ timestampColumnExtra;
|
||||
_ui->_treeWidget->setColumnWidth(0, timestampColumnWidth);
|
||||
_ui->_treeWidget->setColumnWidth(1, 180);
|
||||
_ui->_treeWidget->setColumnCount(5);
|
||||
_ui->_treeWidget->setRootIsDecorated(false);
|
||||
_ui->_treeWidget->setTextElideMode(Qt::ElideMiddle);
|
||||
_ui->_treeWidget->header()->setObjectName("ActivityListHeader");
|
||||
#if defined(Q_OS_MAC)
|
||||
_ui->_treeWidget->setMinimumWidth(400);
|
||||
#endif
|
||||
_ui->_headerLabel->setText(tr("Local sync protocol"));
|
||||
|
||||
QPushButton *copyBtn = _ui->_dialogButtonBox->addButton(tr("Copy"), QDialogButtonBox::ActionRole);
|
||||
copyBtn->setToolTip(tr("Copy the activity list to the clipboard."));
|
||||
copyBtn->setEnabled(true);
|
||||
connect(copyBtn, &QAbstractButton::clicked, this, &ProtocolWidget::copyToClipboard);
|
||||
}
|
||||
|
||||
ProtocolWidget::~ProtocolWidget()
|
||||
{
|
||||
delete _ui;
|
||||
}
|
||||
|
||||
void ProtocolWidget::showEvent(QShowEvent *ev)
|
||||
{
|
||||
ConfigFile cfg;
|
||||
cfg.restoreGeometryHeader(_ui->_treeWidget->header());
|
||||
|
||||
// Sorting by section was newly enabled. But if we restore the header
|
||||
// from a state where sorting was disabled, both of these flags will be
|
||||
// false and sorting will be impossible!
|
||||
_ui->_treeWidget->header()->setSectionsClickable(true);
|
||||
_ui->_treeWidget->header()->setSortIndicatorShown(true);
|
||||
|
||||
// Switch back to "by time" ordering
|
||||
_ui->_treeWidget->sortByColumn(0, Qt::DescendingOrder);
|
||||
|
||||
QWidget::showEvent(ev);
|
||||
}
|
||||
|
||||
void ProtocolWidget::hideEvent(QHideEvent *ev)
|
||||
{
|
||||
ConfigFile cfg;
|
||||
cfg.saveGeometryHeader(_ui->_treeWidget->header());
|
||||
QWidget::hideEvent(ev);
|
||||
}
|
||||
|
||||
void ProtocolWidget::slotItemContextMenu(const QPoint &pos)
|
||||
{
|
||||
auto item = _ui->_treeWidget->itemAt(pos);
|
||||
if (!item)
|
||||
return;
|
||||
auto globalPos = _ui->_treeWidget->viewport()->mapToGlobal(pos);
|
||||
ProtocolItem::openContextMenu(globalPos, item, this);
|
||||
}
|
||||
|
||||
void ProtocolWidget::slotOpenFile(QTreeWidgetItem *item, int)
|
||||
{
|
||||
QString fileName = item->text(1);
|
||||
if (Folder *folder = ProtocolItem::folder(item)) {
|
||||
// folder->path() always comes back with trailing path
|
||||
QString fullPath = folder->path() + fileName;
|
||||
if (QFile(fullPath).exists()) {
|
||||
showInFileManager(fullPath);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void ProtocolWidget::slotItemCompleted(const QString &folder, const SyncFileItemPtr &item)
|
||||
{
|
||||
if (!item->showInProtocolTab())
|
||||
return;
|
||||
QTreeWidgetItem *line = ProtocolItem::create(folder, *item);
|
||||
if (line) {
|
||||
// Limit the number of items
|
||||
int itemCnt = _ui->_treeWidget->topLevelItemCount();
|
||||
while (itemCnt > 2000) {
|
||||
delete _ui->_treeWidget->takeTopLevelItem(itemCnt - 1);
|
||||
itemCnt--;
|
||||
}
|
||||
_ui->_treeWidget->insertTopLevelItem(0, line);
|
||||
}
|
||||
}
|
||||
|
||||
void ProtocolWidget::storeSyncActivity(QTextStream &ts)
|
||||
{
|
||||
int topLevelItems = _ui->_treeWidget->topLevelItemCount();
|
||||
|
||||
for (int i = 0; i < topLevelItems; i++) {
|
||||
QTreeWidgetItem *child = _ui->_treeWidget->topLevelItem(i);
|
||||
ts << right
|
||||
// time stamp
|
||||
<< qSetFieldWidth(20)
|
||||
<< child->data(0, Qt::DisplayRole).toString()
|
||||
// separator
|
||||
<< qSetFieldWidth(0) << ","
|
||||
|
||||
// file name
|
||||
<< qSetFieldWidth(64)
|
||||
<< child->data(1, Qt::DisplayRole).toString()
|
||||
// separator
|
||||
<< qSetFieldWidth(0) << ","
|
||||
|
||||
// folder
|
||||
<< qSetFieldWidth(30)
|
||||
<< child->data(2, Qt::DisplayRole).toString()
|
||||
// separator
|
||||
<< qSetFieldWidth(0) << ","
|
||||
|
||||
// action
|
||||
<< qSetFieldWidth(15)
|
||||
<< child->data(3, Qt::DisplayRole).toString()
|
||||
// separator
|
||||
<< qSetFieldWidth(0) << ","
|
||||
|
||||
// size
|
||||
<< qSetFieldWidth(10)
|
||||
<< child->data(4, Qt::DisplayRole).toString()
|
||||
<< qSetFieldWidth(0)
|
||||
<< endl;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
|
@ -1,112 +0,0 @@
|
|||
/*
|
||||
* Copyright (C) by Klaas Freitag <freitag@owncloud.com>
|
||||
*
|
||||
* 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 PROTOCOLWIDGET_H
|
||||
#define PROTOCOLWIDGET_H
|
||||
|
||||
#include <QDialog>
|
||||
#include <QDateTime>
|
||||
#include <QLocale>
|
||||
|
||||
#include "progressdispatcher.h"
|
||||
#include "owncloudgui.h"
|
||||
|
||||
#include "ui_protocolwidget.h"
|
||||
|
||||
class QPushButton;
|
||||
|
||||
namespace OCC {
|
||||
class SyncResult;
|
||||
|
||||
namespace Ui {
|
||||
class ProtocolWidget;
|
||||
}
|
||||
class Application;
|
||||
|
||||
/**
|
||||
* The items used in the protocol and issue QTreeWidget
|
||||
*
|
||||
* Special sorting: It allows items for global entries to be moved to the top if the
|
||||
* sorting section is the "Time" column.
|
||||
*/
|
||||
class ProtocolItem : public QTreeWidgetItem
|
||||
{
|
||||
public:
|
||||
using QTreeWidgetItem::QTreeWidgetItem;
|
||||
|
||||
// Shared with IssueWidget
|
||||
static ProtocolItem *create(const QString &folder, const SyncFileItem &item);
|
||||
static QString timeString(QDateTime dt, QLocale::FormatType format = QLocale::NarrowFormat);
|
||||
|
||||
struct ExtraData
|
||||
{
|
||||
ExtraData()
|
||||
: status(SyncFileItem::NoStatus)
|
||||
, direction(SyncFileItem::None)
|
||||
{
|
||||
}
|
||||
|
||||
QString path;
|
||||
QString folderName;
|
||||
QDateTime timestamp;
|
||||
quint64 size = 0;
|
||||
SyncFileItem::Status status BITFIELD(4);
|
||||
SyncFileItem::Direction direction BITFIELD(3);
|
||||
};
|
||||
|
||||
static ExtraData extraData(const QTreeWidgetItem *item);
|
||||
static void setExtraData(QTreeWidgetItem *item, const ExtraData &data);
|
||||
|
||||
static SyncJournalFileRecord syncJournalRecord(QTreeWidgetItem *item);
|
||||
static Folder *folder(QTreeWidgetItem *item);
|
||||
|
||||
static void openContextMenu(QPoint globalPos, QTreeWidgetItem *item, QWidget *parent);
|
||||
|
||||
private:
|
||||
bool operator<(const QTreeWidgetItem &other) const override;
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief The ProtocolWidget class
|
||||
* @ingroup gui
|
||||
*/
|
||||
class ProtocolWidget : public QWidget
|
||||
{
|
||||
Q_OBJECT
|
||||
public:
|
||||
explicit ProtocolWidget(QWidget *parent = 0);
|
||||
~ProtocolWidget();
|
||||
QSize sizeHint() const { return ownCloudGui::settingsDialogSize(); }
|
||||
|
||||
void storeSyncActivity(QTextStream &ts);
|
||||
|
||||
public slots:
|
||||
void slotItemCompleted(const QString &folder, const SyncFileItemPtr &item);
|
||||
void slotOpenFile(QTreeWidgetItem *item, int);
|
||||
|
||||
protected:
|
||||
void showEvent(QShowEvent *);
|
||||
void hideEvent(QHideEvent *);
|
||||
|
||||
private slots:
|
||||
void slotItemContextMenu(const QPoint &pos);
|
||||
|
||||
signals:
|
||||
void copyToClipboard();
|
||||
|
||||
private:
|
||||
Ui::ProtocolWidget *_ui;
|
||||
};
|
||||
}
|
||||
#endif // PROTOCOLWIDGET_H
|
|
@ -1,73 +0,0 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<ui version="4.0">
|
||||
<class>OCC::ProtocolWidget</class>
|
||||
<widget class="QWidget" name="OCC::ProtocolWidget">
|
||||
<property name="geometry">
|
||||
<rect>
|
||||
<x>0</x>
|
||||
<y>0</y>
|
||||
<width>612</width>
|
||||
<height>515</height>
|
||||
</rect>
|
||||
</property>
|
||||
<property name="windowTitle">
|
||||
<string>Form</string>
|
||||
</property>
|
||||
<layout class="QGridLayout" name="gridLayout">
|
||||
<item row="0" column="0">
|
||||
<widget class="QLabel" name="_headerLabel">
|
||||
<property name="text">
|
||||
<string>TextLabel</string>
|
||||
</property>
|
||||
<property name="textFormat">
|
||||
<enum>Qt::PlainText</enum>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="1" column="0" colspan="2">
|
||||
<widget class="QTreeWidget" name="_treeWidget">
|
||||
<property name="alternatingRowColors">
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
<property name="rootIsDecorated">
|
||||
<bool>false</bool>
|
||||
</property>
|
||||
<property name="uniformRowHeights">
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
<property name="sortingEnabled">
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
<property name="columnCount">
|
||||
<number>4</number>
|
||||
</property>
|
||||
<column>
|
||||
<property name="text">
|
||||
<string notr="true">1</string>
|
||||
</property>
|
||||
</column>
|
||||
<column>
|
||||
<property name="text">
|
||||
<string notr="true">2</string>
|
||||
</property>
|
||||
</column>
|
||||
<column>
|
||||
<property name="text">
|
||||
<string notr="true">3</string>
|
||||
</property>
|
||||
</column>
|
||||
<column>
|
||||
<property name="text">
|
||||
<string notr="true">4</string>
|
||||
</property>
|
||||
</column>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="2" column="0" colspan="2">
|
||||
<widget class="QDialogButtonBox" name="_dialogButtonBox"/>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
<resources/>
|
||||
<connections/>
|
||||
</ui>
|
|
@ -17,6 +17,8 @@
|
|||
#include "capabilities.h"
|
||||
#include "networkjobs.h"
|
||||
|
||||
#include "iconjob.h"
|
||||
|
||||
#include <QJsonDocument>
|
||||
#include <QJsonObject>
|
||||
|
||||
|
@ -28,37 +30,41 @@ const QString notificationsPath = QLatin1String("ocs/v2.php/apps/notifications/a
|
|||
const char propertyAccountStateC[] = "oc_account_state";
|
||||
const int successStatusCode = 200;
|
||||
const int notModifiedStatusCode = 304;
|
||||
QMap<int, QIcon> ServerNotificationHandler::iconCache;
|
||||
|
||||
ServerNotificationHandler::ServerNotificationHandler(QObject *parent)
|
||||
ServerNotificationHandler::ServerNotificationHandler(AccountState *accountState, QObject *parent)
|
||||
: QObject(parent)
|
||||
, _accountState(accountState)
|
||||
{
|
||||
}
|
||||
|
||||
void ServerNotificationHandler::slotFetchNotifications(AccountState *ptr)
|
||||
void ServerNotificationHandler::slotFetchNotifications()
|
||||
{
|
||||
// check connectivity and credentials
|
||||
if (!(ptr && ptr->isConnected() && ptr->account() && ptr->account()->credentials() && ptr->account()->credentials()->ready())) {
|
||||
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 (ptr->account()->capabilities().isValid()) {
|
||||
if (!ptr->account()->capabilities().notificationsAvailable()) {
|
||||
qCInfo(lcServerNotification) << "Account" << ptr->account()->displayName() << "does not have notifications enabled.";
|
||||
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(ptr->account(), notificationsPath, this);
|
||||
_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 *>(ptr));
|
||||
_notificationJob->addRawHeader("If-None-Match", ptr->notificationsEtagResponseHeader());
|
||||
_notificationJob->setProperty(propertyAccountStateC, QVariant::fromValue<AccountState *>(_accountState));
|
||||
_notificationJob->addRawHeader("If-None-Match", _accountState->notificationsEtagResponseHeader());
|
||||
_notificationJob->start();
|
||||
}
|
||||
|
||||
|
@ -70,6 +76,12 @@ void ServerNotificationHandler::slotEtagResponseHeaderReceived(const QByteArray
|
|||
}
|
||||
}
|
||||
|
||||
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) {
|
||||
|
@ -96,18 +108,28 @@ void ServerNotificationHandler::slotNotificationsReceived(const QJsonDocument &j
|
|||
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();
|
||||
|
||||
QString s = json.value("link").toString();
|
||||
if (!s.isEmpty()) {
|
||||
QUrl link(s);
|
||||
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());
|
||||
}
|
||||
a._link = link;
|
||||
}
|
||||
a._link = link;
|
||||
a._dateTime = QDateTime::fromString(json.value("datetime").toString(), Qt::ISODate);
|
||||
|
||||
auto actions = json.value("actions").toArray();
|
||||
|
|
|
@ -27,20 +27,23 @@ class ServerNotificationHandler : public QObject
|
|||
{
|
||||
Q_OBJECT
|
||||
public:
|
||||
explicit ServerNotificationHandler(QObject *parent = 0);
|
||||
explicit ServerNotificationHandler(AccountState *accountState, QObject *parent = 0);
|
||||
static QMap<int, QIcon> iconCache;
|
||||
|
||||
signals:
|
||||
void newNotificationList(ActivityList);
|
||||
|
||||
public slots:
|
||||
void slotFetchNotifications(AccountState *ptr);
|
||||
void slotFetchNotifications();
|
||||
|
||||
private slots:
|
||||
void slotNotificationsReceived(const QJsonDocument &json, int statusCode);
|
||||
void slotEtagResponseHeaderReceived(const QByteArray &value, int statusCode);
|
||||
void slotIconDownloaded(QByteArray iconData);
|
||||
|
||||
private:
|
||||
QPointer<JsonApiJob> _notificationJob;
|
||||
AccountState *_accountState;
|
||||
};
|
||||
}
|
||||
|
||||
|
|
|
@ -25,7 +25,6 @@
|
|||
#include "owncloudgui.h"
|
||||
#include "activitywidget.h"
|
||||
#include "accountmanager.h"
|
||||
#include "protocolwidget.h"
|
||||
|
||||
#include <QLabel>
|
||||
#include <QStandardItemModel>
|
||||
|
@ -44,8 +43,8 @@
|
|||
|
||||
namespace {
|
||||
const char TOOLBAR_CSS[] =
|
||||
"QToolBar { background: %1; margin: 0; padding: 0; border: none; border-bottom: 1px solid %2; spacing: 0; } "
|
||||
"QToolBar QToolButton { background: %1; border: none; border-bottom: 1px solid %2; margin: 0; padding: 5px; } "
|
||||
"QToolBar { background: %1; margin: 0; padding: 0; border: none; border-bottom: 0 solid %2; spacing: 0; } "
|
||||
"QToolBar QToolButton { background: %1; border: none; border-bottom: 0 solid %2; margin: 0; padding: 5px; } "
|
||||
"QToolBar QToolBarExtension { padding:0; } "
|
||||
"QToolBar QToolButton:checked { background: %3; color: %4; }";
|
||||
|
||||
|
@ -84,19 +83,27 @@ SettingsDialog::SettingsDialog(ownCloudGui *gui, QWidget *parent)
|
|||
setObjectName("Settings"); // required as group for saveGeometry call
|
||||
setWindowTitle(Theme::instance()->appNameGUI());
|
||||
|
||||
connect(AccountManager::instance(), &AccountManager::accountAdded,
|
||||
this, &SettingsDialog::accountAdded);
|
||||
connect(AccountManager::instance(), &AccountManager::accountRemoved,
|
||||
this, &SettingsDialog::accountRemoved);
|
||||
|
||||
_actionGroup = new QActionGroup(this);
|
||||
_actionGroup->setExclusive(true);
|
||||
connect(_actionGroup, &QActionGroup::triggered, this, &SettingsDialog::slotSwitchPage);
|
||||
|
||||
// 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
|
||||
_activityAction = createColorAwareAction(QLatin1String(":/client/resources/activity.png"), tr("Activity"));
|
||||
_actionGroup->addAction(_activityAction);
|
||||
_toolBar->addAction(_activityAction);
|
||||
_activitySettings = new ActivitySettings;
|
||||
_ui->stack->addWidget(_activitySettings);
|
||||
connect(_activitySettings, &ActivitySettings::guiLog, _gui,
|
||||
&ownCloudGui::slotShowOptionalTrayMessage);
|
||||
_activitySettings->setNotificationRefreshInterval(cfg.notificationRefreshInterval());
|
||||
foreach(auto ai, AccountManager::instance()->accounts()) {
|
||||
accountAdded(ai.data());
|
||||
}
|
||||
|
||||
_actionBefore = new QAction;
|
||||
_toolBar->addAction(_actionBefore);
|
||||
|
||||
// Adds space between users + activities and general + network actions
|
||||
QWidget* spacer = new QWidget();
|
||||
spacer->setMinimumWidth(10);
|
||||
spacer->setSizePolicy(QSizePolicy::MinimumExpanding, QSizePolicy::Minimum);
|
||||
_toolBar->addWidget(spacer);
|
||||
|
||||
QAction *generalAction = createColorAwareAction(QLatin1String(":/client/resources/settings.png"), tr("General"));
|
||||
_actionGroup->addAction(generalAction);
|
||||
|
@ -110,20 +117,9 @@ SettingsDialog::SettingsDialog(ownCloudGui *gui, QWidget *parent)
|
|||
NetworkSettings *networkSettings = new NetworkSettings;
|
||||
_ui->stack->addWidget(networkSettings);
|
||||
|
||||
_actionGroupWidgets.insert(_activityAction, _activitySettings);
|
||||
_actionGroupWidgets.insert(generalAction, generalSettings);
|
||||
_actionGroupWidgets.insert(networkAction, networkSettings);
|
||||
|
||||
connect(_actionGroup, &QActionGroup::triggered, this, &SettingsDialog::slotSwitchPage);
|
||||
|
||||
connect(AccountManager::instance(), &AccountManager::accountAdded,
|
||||
this, &SettingsDialog::accountAdded);
|
||||
connect(AccountManager::instance(), &AccountManager::accountRemoved,
|
||||
this, &SettingsDialog::accountRemoved);
|
||||
foreach (auto ai, AccountManager::instance()->accounts()) {
|
||||
accountAdded(ai.data());
|
||||
}
|
||||
|
||||
QTimer::singleShot(1, this, &SettingsDialog::showFirstPage);
|
||||
|
||||
QPushButton *closeButton = _ui->buttonBox->button(QDialogButtonBox::Close);
|
||||
|
@ -189,25 +185,43 @@ void SettingsDialog::showFirstPage()
|
|||
|
||||
void SettingsDialog::showActivityPage()
|
||||
{
|
||||
if (_activityAction) {
|
||||
_activityAction->trigger();
|
||||
if (auto account = qvariant_cast<AccountState*>(sender()->property("account"))) {
|
||||
_activitySettings[account]->show();
|
||||
_ui->stack->setCurrentWidget(_activitySettings[account]);
|
||||
}
|
||||
}
|
||||
|
||||
void SettingsDialog::showIssuesList(const QString &folderAlias)
|
||||
{
|
||||
if (!_activityAction)
|
||||
return;
|
||||
_activityAction->trigger();
|
||||
_activitySettings->slotShowIssuesTab(folderAlias);
|
||||
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)
|
||||
{
|
||||
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();
|
||||
|
@ -223,7 +237,8 @@ void SettingsDialog::accountAdded(AccountState *s)
|
|||
accountAction->setToolTip(s->account()->displayName());
|
||||
accountAction->setIconText(SettingsDialogCommon::shortDisplayNameForSettings(s->account().data(), height * buttonSizeRatio));
|
||||
}
|
||||
_toolBar->insertAction(_toolBar->actions().at(0), accountAction);
|
||||
|
||||
_toolBar->insertAction(_actionBefore, accountAction);
|
||||
auto accountSettings = new AccountSettings(s, this);
|
||||
_ui->stack->insertWidget(0, accountSettings);
|
||||
_actionGroup->addAction(accountAction);
|
||||
|
@ -233,13 +248,13 @@ void SettingsDialog::accountAdded(AccountState *s)
|
|||
connect(accountSettings, &AccountSettings::folderChanged, _gui, &ownCloudGui::slotFoldersChanged);
|
||||
connect(accountSettings, &AccountSettings::openFolderAlias,
|
||||
_gui, &ownCloudGui::slotFolderOpenAction);
|
||||
connect(accountSettings, &AccountSettings::showIssuesList, this, &SettingsDialog::showIssuesList);
|
||||
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);
|
||||
|
||||
activityAdded(s);
|
||||
slotRefreshActivity(s);
|
||||
}
|
||||
|
||||
|
@ -295,7 +310,19 @@ void SettingsDialog::accountRemoved(AccountState *s)
|
|||
if (_actionForAccount.contains(s->account().data())) {
|
||||
_actionForAccount.remove(s->account().data());
|
||||
}
|
||||
_activitySettings->slotRemoveAccount(s);
|
||||
|
||||
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
|
||||
|
@ -317,9 +344,8 @@ void SettingsDialog::customizeStyle()
|
|||
QIcon icon = createColorAwareIcon(a->property("iconPath").toString());
|
||||
a->setIcon(icon);
|
||||
QToolButton *btn = qobject_cast<QToolButton *>(_toolBar->widgetForAction(a));
|
||||
if (btn) {
|
||||
if (btn)
|
||||
btn->setIcon(icon);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -359,8 +385,6 @@ public:
|
|||
btn->setDefaultAction(this);
|
||||
btn->setToolButtonStyle(Qt::ToolButtonTextUnderIcon);
|
||||
btn->setSizePolicy(QSizePolicy::Fixed, QSizePolicy::Expanding);
|
||||
// btn->setMinimumWidth(qMax<int>(parent->sizeHint().height() * buttonSizeRatio,
|
||||
// btn->sizeHint().width()));
|
||||
return btn;
|
||||
}
|
||||
};
|
||||
|
@ -389,9 +413,8 @@ void SettingsDialog::slotRefreshActivityAccountStateSender()
|
|||
|
||||
void SettingsDialog::slotRefreshActivity(AccountState *accountState)
|
||||
{
|
||||
if (accountState) {
|
||||
_activitySettings->slotRefresh(accountState);
|
||||
}
|
||||
if (accountState->isConnected())
|
||||
_activitySettings[accountState]->slotRefresh();
|
||||
}
|
||||
|
||||
} // namespace OCC
|
||||
|
|
|
@ -56,7 +56,7 @@ public:
|
|||
public slots:
|
||||
void showFirstPage();
|
||||
void showActivityPage();
|
||||
void showIssuesList(const QString &folderAlias);
|
||||
// void showIssuesList(const QString &folderAlias);
|
||||
void slotSwitchPage(QAction *action);
|
||||
void slotRefreshActivity(AccountState *accountState);
|
||||
void slotRefreshActivityAccountStateSender();
|
||||
|
@ -74,6 +74,7 @@ private slots:
|
|||
|
||||
private:
|
||||
void customizeStyle();
|
||||
void activityAdded(AccountState *);
|
||||
|
||||
QIcon createColorAwareIcon(const QString &name);
|
||||
QAction *createColorAwareAction(const QString &iconName, const QString &fileName);
|
||||
|
@ -82,6 +83,7 @@ private:
|
|||
Ui::SettingsDialog *const _ui;
|
||||
|
||||
QActionGroup *_actionGroup;
|
||||
QAction *_actionBefore;
|
||||
// Maps the actions from the action group to the corresponding widgets
|
||||
QHash<QAction *, QWidget *> _actionGroupWidgets;
|
||||
|
||||
|
@ -90,10 +92,8 @@ private:
|
|||
QHash<Account *, QAction *> _actionForAccount;
|
||||
|
||||
QToolBar *_toolBar;
|
||||
QMap<AccountState *, ActivitySettings *> _activitySettings;
|
||||
|
||||
ActivitySettings *_activitySettings;
|
||||
|
||||
QAction *_activityAction;
|
||||
ownCloudGui *_gui;
|
||||
};
|
||||
}
|
||||
|
|
|
@ -52,7 +52,75 @@
|
|||
</widget>
|
||||
</item>
|
||||
<item row="0" column="0">
|
||||
<widget class="QStackedWidget" name="stack"/>
|
||||
<widget class="QStackedWidget" name="stack">
|
||||
<property name="palette">
|
||||
<palette>
|
||||
<active>
|
||||
<colorrole role="Base">
|
||||
<brush brushstyle="SolidPattern">
|
||||
<color alpha="255">
|
||||
<red>252</red>
|
||||
<green>252</green>
|
||||
<blue>252</blue>
|
||||
</color>
|
||||
</brush>
|
||||
</colorrole>
|
||||
<colorrole role="Window">
|
||||
<brush brushstyle="SolidPattern">
|
||||
<color alpha="255">
|
||||
<red>255</red>
|
||||
<green>255</green>
|
||||
<blue>255</blue>
|
||||
</color>
|
||||
</brush>
|
||||
</colorrole>
|
||||
</active>
|
||||
<inactive>
|
||||
<colorrole role="Base">
|
||||
<brush brushstyle="SolidPattern">
|
||||
<color alpha="255">
|
||||
<red>252</red>
|
||||
<green>252</green>
|
||||
<blue>252</blue>
|
||||
</color>
|
||||
</brush>
|
||||
</colorrole>
|
||||
<colorrole role="Window">
|
||||
<brush brushstyle="SolidPattern">
|
||||
<color alpha="255">
|
||||
<red>255</red>
|
||||
<green>255</green>
|
||||
<blue>255</blue>
|
||||
</color>
|
||||
</brush>
|
||||
</colorrole>
|
||||
</inactive>
|
||||
<disabled>
|
||||
<colorrole role="Base">
|
||||
<brush brushstyle="SolidPattern">
|
||||
<color alpha="255">
|
||||
<red>255</red>
|
||||
<green>255</green>
|
||||
<blue>255</blue>
|
||||
</color>
|
||||
</brush>
|
||||
</colorrole>
|
||||
<colorrole role="Window">
|
||||
<brush brushstyle="SolidPattern">
|
||||
<color alpha="255">
|
||||
<red>255</red>
|
||||
<green>255</green>
|
||||
<blue>255</blue>
|
||||
</color>
|
||||
</brush>
|
||||
</colorrole>
|
||||
</disabled>
|
||||
</palette>
|
||||
</property>
|
||||
<property name="lineWidth">
|
||||
<number>0</number>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
|
|
|
@ -26,7 +26,6 @@
|
|||
#include "configfile.h"
|
||||
#include "progressdispatcher.h"
|
||||
#include "owncloudgui.h"
|
||||
#include "protocolwidget.h"
|
||||
#include "activitywidget.h"
|
||||
#include "accountmanager.h"
|
||||
|
||||
|
@ -72,7 +71,6 @@ SettingsDialogMac::SettingsDialogMac(ownCloudGui *gui, QWidget *parent)
|
|||
// dialog from minimize is broken in MacPreferencesWindow
|
||||
setWindowFlags(Qt::Window | Qt::WindowTitleHint | Qt::CustomizeWindowHint | Qt::WindowCloseButtonHint | Qt::WindowMaximizeButtonHint);
|
||||
|
||||
|
||||
// Emulate dialog behavior: Escape means close
|
||||
QAction *closeDialogAction = new QAction(this);
|
||||
closeDialogAction->setShortcut(QKeySequence(Qt::Key_Escape));
|
||||
|
@ -93,16 +91,12 @@ SettingsDialogMac::SettingsDialogMac(ownCloudGui *gui, QWidget *parent)
|
|||
|
||||
setWindowTitle(tr("%1").arg(Theme::instance()->appNameGUI()));
|
||||
|
||||
QIcon activityIcon(QLatin1String(":/client/resources/activity.png"));
|
||||
_activitySettings = new ActivitySettings;
|
||||
addPreferencesPanel(activityIcon, tr("Activity"), _activitySettings);
|
||||
connect(_activitySettings, SIGNAL(guiLog(QString, QString)), _gui,
|
||||
SLOT(slotShowOptionalTrayMessage(QString, QString)));
|
||||
|
||||
connect(AccountManager::instance(), &AccountManager::accountAdded,
|
||||
this, &SettingsDialogMac::accountAdded);
|
||||
connect(AccountManager::instance(), &AccountManager::accountRemoved,
|
||||
this, &SettingsDialogMac::accountRemoved);
|
||||
|
||||
_actionsIdx = -1;
|
||||
foreach (auto ai, AccountManager::instance()->accounts()) {
|
||||
accountAdded(ai.data());
|
||||
}
|
||||
|
@ -122,7 +116,6 @@ SettingsDialogMac::SettingsDialogMac(ownCloudGui *gui, QWidget *parent)
|
|||
|
||||
ConfigFile cfg;
|
||||
cfg.restoreGeometry(this);
|
||||
_activitySettings->setNotificationRefreshInterval(cfg.notificationRefreshInterval());
|
||||
}
|
||||
|
||||
void SettingsDialogMac::closeEvent(QCloseEvent *event)
|
||||
|
@ -138,26 +131,17 @@ void SettingsDialogMac::showActivityPage()
|
|||
setCurrentPanelIndex(preferencePanelCount() - 1 - 2);
|
||||
}
|
||||
|
||||
void SettingsDialogMac::showIssuesList(const QString &folderAlias)
|
||||
{
|
||||
// Count backwards (0-based) from the last panel (multiple accounts can be on the left)
|
||||
setCurrentPanelIndex(preferencePanelCount() - 1 - 2);
|
||||
_activitySettings->slotShowIssuesTab(folderAlias);
|
||||
}
|
||||
|
||||
|
||||
void SettingsDialogMac::accountAdded(AccountState *s)
|
||||
{
|
||||
QIcon accountIcon = MacStandardIcon::icon(MacStandardIcon::UserAccounts);
|
||||
auto accountSettings = new AccountSettings(s, this);
|
||||
|
||||
QString displayName = Theme::instance()->multiAccount() ? SettingsDialogCommon::shortDisplayNameForSettings(s->account().data(), 0) : tr("Account");
|
||||
|
||||
insertPreferencesPanel(0, accountIcon, displayName, accountSettings);
|
||||
// this adds the panel - nothing to add here just to fix the order
|
||||
insertPreferencesPanel(++_actionsIdx, accountIcon, displayName, accountSettings);
|
||||
|
||||
connect(accountSettings, &AccountSettings::folderChanged, _gui, &ownCloudGui::slotFoldersChanged);
|
||||
connect(accountSettings, &AccountSettings::openFolderAlias, _gui, &ownCloudGui::slotFolderOpenAction);
|
||||
connect(accountSettings, &AccountSettings::showIssuesList, this, &SettingsDialogMac::showIssuesList);
|
||||
|
||||
connect(s->account().data(), &Account::accountChangedAvatar, this, &SettingsDialogMac::slotAccountAvatarChanged);
|
||||
connect(s->account().data(), &Account::accountChangedDisplayName, this, &SettingsDialogMac::slotAccountDisplayNameChanged);
|
||||
|
@ -165,6 +149,23 @@ void SettingsDialogMac::accountAdded(AccountState *s)
|
|||
// Refresh immediatly when getting online
|
||||
connect(s, &AccountState::isConnectedChanged, this, &SettingsDialogMac::slotRefreshActivityAccountStateSender);
|
||||
|
||||
// Add activity panel
|
||||
QIcon activityIcon(QLatin1String(":/client/resources/activity.png"));
|
||||
_activitySettings[s] = new ActivitySettings(s, this);
|
||||
insertPreferencesPanel(++_actionsIdx, activityIcon, tr("Activity"), _activitySettings[s]);
|
||||
connect(_activitySettings[s], SIGNAL(guiLog(QString, QString)), _gui,
|
||||
SLOT(slotShowOptionalTrayMessage(QString, QString)));
|
||||
|
||||
// if this is not the first account, add separator 2 positions before int the toolbar
|
||||
if(AccountManager::instance()->accounts().first().data() != s &&
|
||||
AccountManager::instance()->accounts().size() >= 1){
|
||||
_separators[s] = insertSeparator(_actionsIdx - 1);
|
||||
++_actionsIdx; //we have one more item in the toolbar
|
||||
}
|
||||
|
||||
ConfigFile cfg;
|
||||
_activitySettings[s]->setNotificationRefreshInterval(cfg.notificationRefreshInterval());
|
||||
|
||||
slotRefreshActivity(s);
|
||||
}
|
||||
|
||||
|
@ -174,10 +175,18 @@ void SettingsDialogMac::accountRemoved(AccountState *s)
|
|||
foreach (auto p, list) {
|
||||
if (p->accountsState() == s) {
|
||||
removePreferencesPanel(p);
|
||||
|
||||
// remove settings panel
|
||||
if(_activitySettings.contains(s))
|
||||
removePreferencesPanel(_activitySettings[s]);
|
||||
|
||||
// remove separator if there is any
|
||||
if(_separators.contains(s)){
|
||||
removeSeparator(_separators[s]);
|
||||
_separators.remove(s);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
_activitySettings->slotRemoveAccount(s);
|
||||
}
|
||||
|
||||
void SettingsDialogMac::slotRefreshActivityAccountStateSender()
|
||||
|
@ -188,7 +197,7 @@ void SettingsDialogMac::slotRefreshActivityAccountStateSender()
|
|||
void SettingsDialogMac::slotRefreshActivity(AccountState *accountState)
|
||||
{
|
||||
if (accountState) {
|
||||
_activitySettings->slotRefresh(accountState);
|
||||
_activitySettings[accountState]->slotRefresh();
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -226,3 +235,4 @@ void SettingsDialogMac::slotAccountDisplayNameChanged()
|
|||
}
|
||||
|
||||
}
|
||||
|
||||
|
|
|
@ -26,7 +26,6 @@ class QListWidgetItem;
|
|||
namespace OCC {
|
||||
|
||||
class AccountSettings;
|
||||
class ProtocolWidget;
|
||||
class Application;
|
||||
class FolderMan;
|
||||
class ownCloudGui;
|
||||
|
@ -47,7 +46,6 @@ public:
|
|||
|
||||
public slots:
|
||||
void showActivityPage();
|
||||
void showIssuesList(const QString &folderAlias);
|
||||
void slotRefreshActivity(AccountState *accountState);
|
||||
void slotRefreshActivityAccountStateSender();
|
||||
|
||||
|
@ -60,8 +58,11 @@ private slots:
|
|||
private:
|
||||
void closeEvent(QCloseEvent *event);
|
||||
|
||||
ProtocolWidget *_protocolWidget;
|
||||
ActivitySettings *_activitySettings;
|
||||
QAction *_actionBefore;
|
||||
int _actionsIdx;
|
||||
QMap<AccountState *, QAction *> _separators;
|
||||
|
||||
QMap<AccountState *, ActivitySettings *> _activitySettings;
|
||||
ownCloudGui *_gui;
|
||||
|
||||
int _protocolIdx;
|
||||
|
@ -69,3 +70,4 @@ private:
|
|||
}
|
||||
|
||||
#endif // SETTINGSDIALOGMAC_H
|
||||
;
|
||||
|
|
|
@ -27,7 +27,7 @@
|
|||
|
||||
namespace OCC {
|
||||
|
||||
SyncLogDialog::SyncLogDialog(QWidget *parent, ProtocolWidget *protoWidget)
|
||||
SyncLogDialog::SyncLogDialog(QWidget *parent)
|
||||
: QDialog(parent)
|
||||
, _ui(new Ui::SyncLogDialog)
|
||||
{
|
||||
|
@ -35,10 +35,6 @@ SyncLogDialog::SyncLogDialog(QWidget *parent, ProtocolWidget *protoWidget)
|
|||
|
||||
_ui->setupUi(this);
|
||||
|
||||
if (protoWidget) {
|
||||
_ui->logWidgetLayout->addWidget(protoWidget);
|
||||
}
|
||||
|
||||
QPushButton *closeButton = _ui->buttonBox->button(QDialogButtonBox::Close);
|
||||
if (closeButton) {
|
||||
connect(closeButton, &QAbstractButton::clicked, this, &QWidget::close);
|
||||
|
|
|
@ -16,8 +16,6 @@
|
|||
#ifndef SyncLogDialog_H
|
||||
#define SyncLogDialog_H
|
||||
|
||||
#include "protocolwidget.h"
|
||||
|
||||
#include <QDialog>
|
||||
|
||||
namespace OCC {
|
||||
|
@ -37,7 +35,7 @@ class SyncLogDialog : public QDialog
|
|||
Q_OBJECT
|
||||
|
||||
public:
|
||||
explicit SyncLogDialog(QWidget *parent = 0, ProtocolWidget *protoWidget = 0);
|
||||
explicit SyncLogDialog(QWidget *parent = 0);
|
||||
~SyncLogDialog();
|
||||
|
||||
private slots:
|
||||
|
|