Add a more functional error view #5516 (#5861)
authorckamm <mail@ckamm.de>
Tue, 4 Jul 2017 12:08:41 +0000 (14:08 +0200)
committerGitHub <noreply@github.com>
Tue, 4 Jul 2017 12:08:41 +0000 (14:08 +0200)
* 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.

16 files changed:
src/gui/CMakeLists.txt
src/gui/accountsettings.cpp
src/gui/accountsettings.h
src/gui/activitywidget.cpp
src/gui/activitywidget.h
src/gui/folderstatusdelegate.cpp
src/gui/folderstatusdelegate.h
src/gui/issueswidget.cpp [new file with mode: 0644]
src/gui/issueswidget.h [new file with mode: 0644]
src/gui/issueswidget.ui [new file with mode: 0644]
src/gui/protocolwidget.cpp
src/gui/protocolwidget.h
src/gui/settingsdialog.cpp
src/gui/settingsdialog.h
src/gui/settingsdialogmac.cpp
src/gui/settingsdialogmac.h

index 643bb932cdaaa84e698db84d2daca4fa2e0c5004..e40c0303467fcbd04939f5aba2eb12d588b399a6 100644 (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
index 2f866502ad66e965fe6eb080f9733e56db0fbc29..6da34d59048d9d45efb2b0e091e70acedae7e9c8 100644 (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"
index 9c4a1fc5696ee61083a7a4e67214a1fe14b2864d..3e45d7e4a7f68931d771c3fdd5c68f8c24d6e4a4 100644 (file)
@@ -60,6 +60,7 @@ public:
 signals:
     void folderChanged();
     void openFolderAlias(const QString &);
+    void showIssuesList(const QString &folderAlias);
 
 public slots:
     void slotOpenOC();
index 2d40ce8ee497c75676f0227a8b313964e4f43077..c4e82884b11073e1c60e7566ce22243c26e53164 100644 (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);
index bae9b6ff951294c20fec91f6024f002904239ae3..e2fa314e059ea2532bb8ac8d47ecb9f2243783eb 100644 (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;
index ddb5038675575f0bd80f028ed05439e21784698f..1d93893747ba03790ad2b914a3607245cbcfe8d6 100644 (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
index b42ae134d01e632cc29b9595159203b07be20806..a3fd5b71d2766eb4f099a0ef40ee624553767b3f 100644 (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:
diff --git a/src/gui/issueswidget.cpp b/src/gui/issueswidget.cpp
new file mode 100644 (file)
index 0000000..eff8a3b
--- /dev/null
@@ -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);
+}
+}
diff --git a/src/gui/issueswidget.h b/src/gui/issueswidget.h
new file mode 100644 (file)
index 0000000..9d31e16
--- /dev/null
@@ -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
diff --git a/src/gui/issueswidget.ui b/src/gui/issueswidget.ui
new file mode 100644 (file)
index 0000000..eb15d3d
--- /dev/null
@@ -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>
index 9803e796bd36e49ac73d03b89711972e7fb0d69a..2f177a1c64bcf1749be8779882282e2ea9f77aa3 100644 (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;
-    }
-}
 }
index 927a23c4b10dbe897cc93034304f13e3198228bc..7e3a154a5ce039b350c75004bb36ba680750f7fe 100644 (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
index bb44a080aa8880a376cf84e0edb3a4c5faf2fee2..f21ecd5f8f75d9d65fb53a1230cc76b9733c3d0b 100644 (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);
index 9d445995994a76b3512a3daa5c2940aad5422cdf..15596a00f0bed6895a2c2ccca89edfff2b981d1d 100644 (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();
index 486bc7018435f6ee48e4dd21e8513205ca35dc2e..db4fad95fc168deed6a4e6eb2ef897c6e2f4af46 100644 (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()));
 
index f931f90095c69d1f10666e36b12ea6a3d9529f1d..c69ee5f140d48994fa1d30466a59d66df6fcc52b 100644 (file)
@@ -47,6 +47,7 @@ public:
 
 public slots:
     void showActivityPage();
+    void showIssuesList(const QString &folderAlias);
     void slotRefreshActivity(AccountState *accountState);
 
 private slots: