Add a more functional error view #5516 (#5861)

* Add a more functional error view #5516

* Allow filtering of ignores and warnings to see only important bits.
* Navigate from the folder view to the error view by clicking on the
  error list with the red background.
* Move the error list into its own ui file to allow easier extension.
* Fix issue around tab id handling in ActivitySettings.
* Rename "Action" column to "Issue".
* Change mouse cursor to hand over button and new error list area

Several OSX fixes provided by guruz.
This commit is contained in:
ckamm 2017-07-04 14:08:41 +02:00 committed by GitHub
parent 9493e8f42e
commit ce8341ca1f
16 changed files with 665 additions and 132 deletions

View file

@ -21,6 +21,7 @@ set(client_UI
ignorelisteditor.ui
networksettings.ui
protocolwidget.ui
issueswidget.ui
activitywidget.ui
synclogdialog.ui
settingsdialog.ui
@ -64,6 +65,7 @@ set(client_SRCS
owncloudgui.cpp
owncloudsetupwizard.cpp
protocolwidget.cpp
issueswidget.cpp
activitydata.cpp
activitylistmodel.cpp
activitywidget.cpp

View file

@ -69,6 +69,42 @@ static const char progressBarStyleC[] =
"background-color: %1; width: 1px;"
"}";
/**
* Adjusts the mouse cursor based on the region it is on over the folder tree view.
*
* Used to show that one can click the red error list box by changing the cursor
* to the pointing hand.
*/
class MouseCursorChanger : public QObject
{
Q_OBJECT
public:
MouseCursorChanger(QObject *parent)
: QObject(parent)
{
}
QTreeView *folderList;
FolderStatusModel *model;
protected:
bool eventFilter(QObject *watched, QEvent *event) override
{
if (event->type() == QEvent::HoverMove) {
Qt::CursorShape shape = Qt::ArrowCursor;
auto pos = folderList->mapFromGlobal(QCursor::pos());
auto index = folderList->indexAt(pos);
if (model->classify(index) == FolderStatusModel::RootFolder
&& (FolderStatusDelegate::errorsListRect(folderList->visualRect(index)).contains(pos)
|| FolderStatusDelegate::optionsButtonRect(folderList->visualRect(index),folderList->layoutDirection()).contains(pos))) {
shape = Qt::PointingHandCursor;
}
folderList->setCursor(shape);
}
return QObject::eventFilter(watched, event);
}
};
AccountSettings::AccountSettings(AccountState *accountState, QWidget *parent)
: QWidget(parent)
, ui(new Ui::AccountSettings)
@ -94,6 +130,13 @@ AccountSettings::AccountSettings(AccountState *accountState, QWidget *parent)
#endif
new ToolTipUpdater(ui->_folderList);
auto mouseCursorChanger = new MouseCursorChanger(this);
mouseCursorChanger->folderList = ui->_folderList;
mouseCursorChanger->model = _model;
ui->_folderList->setMouseTracking(true);
ui->_folderList->setAttribute(Qt::WA_Hover, true);
ui->_folderList->installEventFilter(mouseCursorChanger);
createAccountToolbox();
connect(AccountManager::instance(), SIGNAL(accountAdded(AccountState *)),
SLOT(slotAccountAdded(AccountState *)));
@ -301,6 +344,10 @@ void AccountSettings::slotFolderListClicked(const QModelIndex &indx)
slotCustomContextMenuRequested(pos);
return;
}
if (FolderStatusDelegate::errorsListRect(tv->visualRect(indx)).contains(pos)) {
emit showIssuesList(_model->data(indx, FolderStatusDelegate::FolderAliasRole).toString());
return;
}
// Expand root items on single click
if (_accountState && _accountState->state() == AccountState::Connected) {
@ -808,3 +855,5 @@ bool AccountSettings::event(QEvent *e)
}
} // namespace OCC
#include "accountsettings.moc"

View file

@ -60,6 +60,7 @@ public:
signals:
void folderChanged();
void openFolderAlias(const QString &);
void showIssuesList(const QString &folderAlias);
public slots:
void slotOpenOC();

View file

@ -33,6 +33,7 @@
#include "accountmanager.h"
#include "activityitemdelegate.h"
#include "protocolwidget.h"
#include "issueswidget.h"
#include "QProgressIndicator.h"
#include "notificationwidget.h"
#include "notificationconfirmjob.h"
@ -518,33 +519,23 @@ ActivitySettings::ActivitySettings(QWidget *parent)
_tab = new QTabWidget(this);
hbox->addWidget(_tab);
_activityWidget = new ActivityWidget(this);
_activityTabId = _tab->insertTab(0, _activityWidget, Theme::instance()->applicationIcon(), tr("Server Activity"));
_activityTabId = _tab->addTab(_activityWidget, Theme::instance()->applicationIcon(), tr("Server Activity"));
connect(_activityWidget, SIGNAL(copyToClipboard()), this, SLOT(slotCopyToClipboard()));
connect(_activityWidget, SIGNAL(hideActivityTab(bool)), this, SLOT(setActivityTabHidden(bool)));
connect(_activityWidget, SIGNAL(guiLog(QString, QString)), this, SIGNAL(guiLog(QString, QString)));
connect(_activityWidget, SIGNAL(newNotification()), SLOT(slotShowActivityTab()));
_protocolWidget = new ProtocolWidget(this);
_tab->insertTab(1, _protocolWidget, Theme::instance()->syncStateIcon(SyncResult::Success), tr("Sync Protocol"));
_protocolTabId = _tab->addTab(_protocolWidget, Theme::instance()->syncStateIcon(SyncResult::Success), tr("Sync Protocol"));
connect(_protocolWidget, SIGNAL(copyToClipboard()), this, SLOT(slotCopyToClipboard()));
connect(_protocolWidget, SIGNAL(issueItemCountUpdated(int)),
this, SLOT(slotShowIssueItemCount(int)));
// Add the not-synced list into the tab
QWidget *w = new QWidget;
QVBoxLayout *vbox2 = new QVBoxLayout(w);
vbox2->addWidget(new QLabel(tr("List of ignored or erroneous files"), this));
vbox2->addWidget(_protocolWidget->issueWidget());
QDialogButtonBox *dlgButtonBox = new QDialogButtonBox(this);
vbox2->addWidget(dlgButtonBox);
QPushButton *_copyBtn = dlgButtonBox->addButton(tr("Copy"), QDialogButtonBox::ActionRole);
_copyBtn->setToolTip(tr("Copy the activity list to the clipboard."));
_copyBtn->setEnabled(true);
connect(_copyBtn, SIGNAL(clicked()), this, SLOT(slotCopyToClipboard()));
w->setLayout(vbox2);
_syncIssueTabId = _tab->insertTab(2, w, Theme::instance()->syncStateIcon(SyncResult::Problem), QString());
_issuesWidget = new IssuesWidget(this);
_syncIssueTabId = _tab->addTab(_issuesWidget, Theme::instance()->syncStateIcon(SyncResult::Problem), QString());
slotShowIssueItemCount(0); // to display the label.
connect(_issuesWidget, SIGNAL(issueCountUpdated(int)),
this, SLOT(slotShowIssueItemCount(int)));
connect(_issuesWidget, SIGNAL(copyToClipboard()),
this, SLOT(slotCopyToClipboard()));
// Add a progress indicator to spin if the acitivity list is updated.
_progressIndicator = new QProgressIndicator(this);
@ -571,10 +562,14 @@ 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;
}
}
@ -595,6 +590,15 @@ void ActivitySettings::slotShowActivityTab()
}
}
void ActivitySettings::slotShowIssuesTab(const QString &folderAlias)
{
if (_syncIssueTabId == -1)
return;
_tab->setCurrentIndex(_syncIssueTabId);
_issuesWidget->showFolderErrors(folderAlias);
}
void ActivitySettings::slotCopyToClipboard()
{
QString text;
@ -603,18 +607,18 @@ void ActivitySettings::slotCopyToClipboard()
int idx = _tab->currentIndex();
QString message;
if (idx == 0) {
if (idx == _activityTabId) {
// the activity widget
_activityWidget->storeActivityList(ts);
message = tr("The server activity list has been copied to the clipboard.");
} else if (idx == 1) {
} 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 == 2) {
} else if (idx == _syncIssueTabId) {
// issues Widget
message = tr("The list of unsynced items has been copied to the clipboard.");
_protocolWidget->storeSyncIssues(ts);
_issuesWidget->storeSyncIssues(ts);
}
QApplication::clipboard()->setText(text);

View file

@ -35,6 +35,7 @@ namespace OCC {
class Account;
class AccountStatusPtr;
class ProtocolWidget;
class IssuesWidget;
class JsonApiJob;
class NotificationWidget;
class ActivityListModel;
@ -138,6 +139,8 @@ public slots:
void setNotificationRefreshInterval(quint64 interval);
void slotShowIssuesTab(const QString &folderAlias);
private slots:
void slotCopyToClipboard();
void setActivityTabHidden(bool hidden);
@ -153,10 +156,12 @@ private:
QTabWidget *_tab;
int _activityTabId;
int _protocolTabId;
int _syncIssueTabId;
ActivityWidget *_activityWidget;
ProtocolWidget *_protocolWidget;
IssuesWidget *_issuesWidget;
QProgressIndicator *_progressIndicator;
QTimer _notificationCheckTimer;
QHash<AccountState *, QElapsedTimer> _timeSinceLastCheck;

View file

@ -373,5 +373,15 @@ QRect FolderStatusDelegate::optionsButtonRect(QRect within, Qt::LayoutDirection
return QStyle::visualRect(direction, within, r);
}
QRect FolderStatusDelegate::errorsListRect(QRect within)
{
QFont font = QFont();
QFont aliasFont = makeAliasFont(font);
QFontMetrics fm(font);
QFontMetrics aliasFm(aliasFont);
within.setTop(within.top() + FolderStatusDelegate::rootFolderHeightWithoutErrors(fm, aliasFm));
return within;
}
} // namespace OCC

View file

@ -56,6 +56,7 @@ public:
* return the position of the option button within the item
*/
static QRect optionsButtonRect(QRect within, Qt::LayoutDirection direction);
static QRect errorsListRect(QRect within);
static int rootFolderHeightWithoutErrors(const QFontMetrics &fm, const QFontMetrics &aliasFm);
private:

294
src/gui/issueswidget.cpp Normal file
View file

@ -0,0 +1,294 @@
/*
* 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>
#if QT_VERSION >= QT_VERSION_CHECK(5, 0, 0)
#include <QtWidgets>
#endif
#include "issueswidget.h"
#include "configfile.h"
#include "syncresult.h"
#include "logger.h"
#include "utility.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 "ui_issueswidget.h"
#include <climits>
namespace OCC {
IssuesWidget::IssuesWidget(QWidget *parent)
: QWidget(parent)
, _ui(new Ui::IssuesWidget)
{
_ui->setupUi(this);
connect(ProgressDispatcher::instance(), SIGNAL(progressInfo(QString, ProgressInfo)),
this, SLOT(slotProgressInfo(QString, ProgressInfo)));
connect(ProgressDispatcher::instance(), SIGNAL(itemCompleted(QString, SyncFileItemPtr)),
this, SLOT(slotItemCompleted(QString, SyncFileItemPtr)));
connect(_ui->_treeWidget, SIGNAL(itemActivated(QTreeWidgetItem *, int)), SLOT(slotOpenFile(QTreeWidgetItem *, int)));
connect(_ui->copyIssuesButton, SIGNAL(clicked()), SIGNAL(copyToClipboard()));
connect(_ui->showIgnores, SIGNAL(toggled(bool)), SLOT(slotRefreshIssues()));
connect(_ui->showWarnings, SIGNAL(toggled(bool)), SLOT(slotRefreshIssues()));
connect(_ui->filterAccount, SIGNAL(currentIndexChanged(int)), SLOT(slotRefreshIssues()));
connect(_ui->filterAccount, SIGNAL(currentIndexChanged(int)), SLOT(slotUpdateFolderFilters()));
connect(_ui->filterFolder, SIGNAL(currentIndexChanged(int)), SLOT(slotRefreshIssues()));
for (auto account : AccountManager::instance()->accounts()) {
slotAccountAdded(account.data());
}
connect(AccountManager::instance(), SIGNAL(accountAdded(AccountState *)),
SLOT(slotAccountAdded(AccountState *)));
connect(AccountManager::instance(), SIGNAL(accountRemoved(AccountState *)),
SLOT(slotAccountRemoved(AccountState *)));
// 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(ProtocolWidget::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
}
IssuesWidget::~IssuesWidget()
{
delete _ui;
}
void IssuesWidget::showEvent(QShowEvent *ev)
{
ConfigFile cfg;
cfg.restoreGeometryHeader(_ui->_treeWidget->header());
QWidget::showEvent(ev);
}
void IssuesWidget::hideEvent(QHideEvent *ev)
{
ConfigFile cfg;
cfg.saveGeometryHeader(_ui->_treeWidget->header());
QWidget::hideEvent(ev);
}
void IssuesWidget::cleanItems(const QString &folder)
{
// 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);
QString itemFolder = item->data(2, Qt::UserRole).toString();
if (itemFolder == folder) {
delete item;
}
}
// update the tabtext
emit(issueCountUpdated(_ui->_treeWidget->topLevelItemCount()));
}
void IssuesWidget::slotOpenFile(QTreeWidgetItem *item, int)
{
QString folderName = item->data(2, Qt::UserRole).toString();
QString fileName = item->text(1);
Folder *folder = FolderMan::instance()->folder(folderName);
if (folder) {
// 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.isUpdatingEstimates()) {
// The sync is restarting, clean the old items
cleanItems(folder);
} else if (progress.completedFiles() >= progress.totalFiles()) {
//Sync completed
}
}
void IssuesWidget::slotItemCompleted(const QString &folder, const SyncFileItemPtr &item)
{
if (!item->hasErrorStatus())
return;
QTreeWidgetItem *line = ProtocolWidget::createCompletedTreewidgetItem(folder, *item);
if (!line)
return;
_ui->_treeWidget->insertTopLevelItem(0, line);
line->setHidden(!shouldBeVisible(line, currentAccountFilter(), currentFolderFilter()));
emit issueCountUpdated(_ui->_treeWidget->topLevelItemCount());
}
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));
}
}
void IssuesWidget::slotAccountAdded(AccountState *account)
{
_ui->filterAccount->addItem(account->account()->displayName(), QVariant::fromValue(account));
}
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);
}
}
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 status = item->data(0, Qt::UserRole);
visible &= (_ui->showIgnores->isChecked() || status != SyncFileItem::FileIgnored);
visible &= (_ui->showWarnings->isChecked()
|| (status != SyncFileItem::SoftError
&& status != SyncFileItem::Conflict
&& status != SyncFileItem::Restoration));
auto folderalias = item->data(2, Qt::UserRole).toString();
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 (!account) {
_ui->filterFolder->setCurrentIndex(0);
}
_ui->filterFolder->setEnabled(account != 0);
for (int i = _ui->filterFolder->count() - 1; i >= 1; --i) {
_ui->filterFolder->removeItem(i);
}
for (auto folder : FolderMan::instance()->map().values()) {
if (folder->accountState() != account)
continue;
_ui->filterFolder->addItem(folder->shortGuiLocalPath(), folder->alias());
}
}
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);
}
}

82
src/gui/issueswidget.h Normal file
View file

@ -0,0 +1,82 @@
/*
* 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 "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 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);
private:
AccountState *currentAccountFilter() const;
QString currentFolderFilter() const;
bool shouldBeVisible(QTreeWidgetItem *item, AccountState *filterAccount,
const QString &filterFolderAlias) const;
void cleanItems(const QString &folder);
Ui::IssuesWidget *_ui;
};
}
#endif

161
src/gui/issueswidget.ui Normal file
View file

@ -0,0 +1,161 @@
<?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="formLayout">
<item row="0" column="0">
<widget class="QLabel" name="label">
<property name="text">
<string>Account</string>
</property>
</widget>
</item>
<item row="0" column="1">
<widget class="QComboBox" name="filterAccount">
<item>
<property name="text">
<string>&lt;no filter&gt;</string>
</property>
</item>
</widget>
</item>
<item row="1" column="0">
<widget class="QLabel" name="label_2">
<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>&lt;no filter&gt;</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="uniformRowHeights">
<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">
<item>
<spacer name="horizontalSpacer">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>40</width>
<height>20</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>
</widget>
<resources/>
<connections/>
</ui>

View file

@ -37,13 +37,10 @@ namespace OCC {
ProtocolWidget::ProtocolWidget(QWidget *parent)
: QWidget(parent)
, IgnoredIndicatorRole(Qt::UserRole + 1)
, _ui(new Ui::ProtocolWidget)
{
_ui->setupUi(this);
connect(ProgressDispatcher::instance(), SIGNAL(progressInfo(QString, ProgressInfo)),
this, SLOT(slotProgressInfo(QString, ProgressInfo)));
connect(ProgressDispatcher::instance(), SIGNAL(itemCompleted(QString, SyncFileItemPtr)),
this, SLOT(slotItemCompleted(QString, SyncFileItemPtr)));
@ -81,25 +78,6 @@ ProtocolWidget::ProtocolWidget(QWidget *parent)
copyBtn->setToolTip(tr("Copy the activity list to the clipboard."));
copyBtn->setEnabled(true);
connect(copyBtn, SIGNAL(clicked()), SIGNAL(copyToClipboard()));
// this view is used to display all errors such as real errors, soft errors and ignored files
// it is instantiated here, but made accessible via the method issueWidget() so that it can
// be embedded into another gui element.
_issueItemView = new QTreeWidget(this);
header.removeLast();
_issueItemView->setHeaderLabels(header);
timestampColumnWidth =
ActivityItemDelegate::rowHeight() // icon
+ _issueItemView->fontMetrics().width(timeString(QDateTime::currentDateTime()))
+ timestampColumnExtra;
_issueItemView->setColumnWidth(0, timestampColumnWidth);
_issueItemView->setColumnWidth(1, 180);
_issueItemView->setColumnCount(4);
_issueItemView->setRootIsDecorated(false);
_issueItemView->setTextElideMode(Qt::ElideMiddle);
_issueItemView->header()->setObjectName("ActivityErrorListHeader");
connect(_issueItemView, SIGNAL(itemActivated(QTreeWidgetItem *, int)),
SLOT(slotOpenFile(QTreeWidgetItem *, int)));
}
ProtocolWidget::~ProtocolWidget()
@ -121,23 +99,8 @@ void ProtocolWidget::hideEvent(QHideEvent *ev)
QWidget::hideEvent(ev);
}
void ProtocolWidget::cleanItems(const QString &folder)
{
// The issue list is a state, clear it and let the next sync fill it
// with ignored files and propagation errors.
int itemCnt = _issueItemView->topLevelItemCount();
for (int cnt = itemCnt - 1; cnt >= 0; cnt--) {
QTreeWidgetItem *item = _issueItemView->topLevelItem(cnt);
QString itemFolder = item->data(2, Qt::UserRole).toString();
if (itemFolder == folder) {
delete item;
}
}
// update the tabtext
emit(issueItemCountUpdated(_issueItemView->topLevelItemCount()));
}
QString ProtocolWidget::timeString(QDateTime dt, QLocale::FormatType format) const
QString ProtocolWidget::timeString(QDateTime dt, QLocale::FormatType format)
{
const QLocale loc = QLocale::system();
QString dtFormat = loc.dateTimeFormat(format);
@ -198,50 +161,32 @@ QTreeWidgetItem *ProtocolWidget::createCompletedTreewidgetItem(const QString &fo
}
QTreeWidgetItem *twitem = new QTreeWidgetItem(columns);
if (item._status == SyncFileItem::FileIgnored) {
// Tell that we want to remove it on the next sync.
twitem->setData(0, IgnoredIndicatorRole, true);
}
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);
twitem->setData(0, Qt::UserRole, item._status);
twitem->setData(2, Qt::UserRole, folder);
return twitem;
}
void ProtocolWidget::slotProgressInfo(const QString &folder, const ProgressInfo &progress)
{
if (!progress.isUpdatingEstimates()) {
// The sync is restarting, clean the old items
cleanItems(folder);
} else if (progress.completedFiles() >= progress.totalFiles()) {
//Sync completed
}
}
void ProtocolWidget::slotItemCompleted(const QString &folder, const SyncFileItemPtr &item)
{
if (item->hasErrorStatus())
return;
QTreeWidgetItem *line = createCompletedTreewidgetItem(folder, *item);
if (line) {
if (item->hasErrorStatus()) {
_issueItemView->insertTopLevelItem(0, line);
emit issueItemCountUpdated(_issueItemView->topLevelItemCount());
} else {
// 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);
// 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();
@ -281,36 +226,4 @@ void ProtocolWidget::storeSyncActivity(QTextStream &ts)
}
}
void ProtocolWidget::storeSyncIssues(QTextStream &ts)
{
int topLevelItems = _issueItemView->topLevelItemCount();
for (int i = 0; i < topLevelItems; i++) {
QTreeWidgetItem *child = _issueItemView->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()
<< qSetFieldWidth(0)
<< endl;
}
}
}

View file

@ -46,12 +46,13 @@ public:
~ProtocolWidget();
QSize sizeHint() const { return ownCloudGui::settingsDialogSize(); }
QTreeWidget *issueWidget() { return _issueItemView; }
void storeSyncActivity(QTextStream &ts);
void storeSyncIssues(QTextStream &ts);
// Shared with IssueWidget
static QTreeWidgetItem *createCompletedTreewidgetItem(const QString &folder, const SyncFileItem &item);
static QString timeString(QDateTime dt, QLocale::FormatType format = QLocale::NarrowFormat);
public slots:
void slotProgressInfo(const QString &folder, const ProgressInfo &progress);
void slotItemCompleted(const QString &folder, const SyncFileItemPtr &item);
void slotOpenFile(QTreeWidgetItem *item, int);
@ -61,19 +62,9 @@ protected:
signals:
void copyToClipboard();
void issueItemCountUpdated(int);
private:
void setSyncResultStatus(const SyncResult &result);
void cleanItems(const QString &folder);
QTreeWidgetItem *createCompletedTreewidgetItem(const QString &folder, const SyncFileItem &item);
QString timeString(QDateTime dt, QLocale::FormatType format = QLocale::NarrowFormat) const;
const int IgnoredIndicatorRole;
Ui::ProtocolWidget *_ui;
QTreeWidget *_issueItemView;
};
}
#endif // PROTOCOLWIDGET_H

View file

@ -211,6 +211,14 @@ void SettingsDialog::showActivityPage()
}
}
void SettingsDialog::showIssuesList(const QString &folderAlias)
{
if (!_activityAction)
return;
_activityAction->trigger();
_activitySettings->slotShowIssuesTab(folderAlias);
}
void SettingsDialog::accountAdded(AccountState *s)
{
auto height = _toolBar->sizeHint().height();
@ -242,6 +250,7 @@ void SettingsDialog::accountAdded(AccountState *s)
connect(accountSettings, SIGNAL(folderChanged()), _gui, SLOT(slotFoldersChanged()));
connect(accountSettings, SIGNAL(openFolderAlias(const QString &)),
_gui, SLOT(slotFolderOpenAction(QString)));
connect(accountSettings, SIGNAL(showIssuesList(QString)), SLOT(showIssuesList(QString)));
connect(s->account().data(), SIGNAL(accountChangedAvatar()), SLOT(slotAccountAvatarChanged()));
slotRefreshActivity(s);

View file

@ -56,6 +56,7 @@ public:
public slots:
void showFirstPage();
void showActivityPage();
void showIssuesList(const QString &folderAlias);
void slotSwitchPage(QAction *action);
void slotRefreshActivity(AccountState *accountState);
void slotAccountAvatarChanged();

View file

@ -133,6 +133,14 @@ 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);
@ -144,6 +152,7 @@ void SettingsDialogMac::accountAdded(AccountState *s)
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(), SIGNAL(accountChangedAvatar()), this, SLOT(slotAccountAvatarChanged()));

View file

@ -47,6 +47,7 @@ public:
public slots:
void showActivityPage();
void showIssuesList(const QString &folderAlias);
void slotRefreshActivity(AccountState *accountState);
private slots: