Welcome to mirror list, hosted at ThFree Co, Russian Federation.

github.com/owncloud/client.git - Unnamed repository; edit this file 'description' to name the repository.
summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--changelog/unreleased/81587
-rw-r--r--src/gui/CMakeLists.txt3
-rw-r--r--src/gui/activitylistmodel.cpp7
-rw-r--r--src/gui/activitylistmodel.h2
-rw-r--r--src/gui/activitywidget.cpp50
-rw-r--r--src/gui/activitywidget.h4
-rw-r--r--src/gui/folderman.cpp4
-rw-r--r--src/gui/issueswidget.cpp440
-rw-r--r--src/gui/issueswidget.h40
-rw-r--r--src/gui/issueswidget.ui141
-rw-r--r--src/gui/models.cpp49
-rw-r--r--src/gui/models.h35
-rw-r--r--src/gui/protocolitem.cpp82
-rw-r--r--src/gui/protocolitem.h65
-rw-r--r--src/gui/protocolitemmodel.cpp217
-rw-r--r--src/gui/protocolitemmodel.h77
-rw-r--r--src/gui/protocolwidget.cpp319
-rw-r--r--src/gui/protocolwidget.h62
-rw-r--r--src/gui/protocolwidget.ui40
-rw-r--r--src/gui/settingsdialog.cpp4
-rw-r--r--src/gui/settingsdialog.h2
-rw-r--r--test/modeltests/CMakeLists.txt1
-rw-r--r--test/modeltests/testactivitymodel.cpp2
-rw-r--r--test/modeltests/testprotocolmodel.cpp48
24 files changed, 767 insertions, 934 deletions
diff --git a/changelog/unreleased/8158 b/changelog/unreleased/8158
index 286a9960f..4d5d01d6b 100644
--- a/changelog/unreleased/8158
+++ b/changelog/unreleased/8158
@@ -1,7 +1,8 @@
-Enhancement: We reworked the server activity table
+Enhancement: We reworked the tables
-We redone the server activity table, its now behaves as a proper table,
-is sortable, supports right to left layouts and overall behave more smooth.
+We reworked all the tables in the application to unify
+their behaviour and improve their performance.
https://github.com/owncloud/client/issues/8158
https://github.com/owncloud/client/issues/4336
+https://github.com/owncloud/client/issues/8528
diff --git a/src/gui/CMakeLists.txt b/src/gui/CMakeLists.txt
index f0490de9a..7f0e5ada3 100644
--- a/src/gui/CMakeLists.txt
+++ b/src/gui/CMakeLists.txt
@@ -49,6 +49,7 @@ set(client_SRCS
ignorelisteditor.cpp
lockwatcher.cpp
logbrowser.cpp
+ models.cpp
networksettings.cpp
ocsjob.cpp
ocssharejob.cpp
@@ -57,6 +58,8 @@ set(client_SRCS
owncloudgui.cpp
owncloudsetupwizard.cpp
protocolwidget.cpp
+ protocolitem.cpp
+ protocolitemmodel.cpp
issueswidget.cpp
activitydata.cpp
activitylistmodel.cpp
diff --git a/src/gui/activitylistmodel.cpp b/src/gui/activitylistmodel.cpp
index 238269856..ccc03acd0 100644
--- a/src/gui/activitylistmodel.cpp
+++ b/src/gui/activitylistmodel.cpp
@@ -22,9 +22,10 @@
#include "account.h"
#include "accountstate.h"
#include "accountmanager.h"
-#include "folderman.h"
#include "accessmanager.h"
+#include "folderman.h"
#include "guiutility.h"
+#include "models.h"
#include "activitydata.h"
#include "activitylistmodel.h"
@@ -52,7 +53,7 @@ QVariant ActivityListModel::data(const QModelIndex &index, int role) const
}
const auto column = static_cast<ActivityRole>(index.column());
switch (role) {
- case UnderlyingDataRole:
+ case Models::UnderlyingDataRole:
Q_FALLTHROUGH();
case Qt::DisplayRole:
switch (column) {
@@ -61,7 +62,7 @@ QVariant ActivityListModel::data(const QModelIndex &index, int role) const
case ActivityRole::Text:
return a.subject();
case ActivityRole::PointInTime:
- if (role == UnderlyingDataRole) {
+ if (role == Models::UnderlyingDataRole) {
return a.dateTime();
} else {
return Utility::timeAgoInWords(a.dateTime());
diff --git a/src/gui/activitylistmodel.h b/src/gui/activitylistmodel.h
index 014cae296..8c5b2422f 100644
--- a/src/gui/activitylistmodel.h
+++ b/src/gui/activitylistmodel.h
@@ -38,8 +38,6 @@ class ActivityListModel : public QAbstractTableModel
{
Q_OBJECT
public:
- // TODO: Move to a common namespace
- static constexpr int UnderlyingDataRole = Qt::UserRole + 100;
enum class ActivityRole {
Text,
Account,
diff --git a/src/gui/activitywidget.cpp b/src/gui/activitywidget.cpp
index 94e326d3c..e5e49d085 100644
--- a/src/gui/activitywidget.cpp
+++ b/src/gui/activitywidget.cpp
@@ -17,7 +17,7 @@
#include "activitylistmodel.h"
#include "activitywidget.h"
-#include "configfile.h">
+#include "configfile.h"
#include "syncresult.h"
#include "logger.h"
#include "theme.h"
@@ -29,6 +29,7 @@
#include "account.h"
#include "accountstate.h"
#include "accountmanager.h"
+#include "models.h"
#include "protocolwidget.h"
#include "issueswidget.h"
#include "QProgressIndicator.h"
@@ -55,18 +56,13 @@ ActivityWidget::ActivityWidget(QWidget *parent)
{
_ui->setupUi(this);
-// Adjust copyToClipboard() when making changes here!
-#if defined(Q_OS_MAC)
- _ui->_activityList->setMinimumWidth(400);
-#endif
-
_model = new ActivityListModel(this);
auto sortModel = new QSortFilterProxyModel(this);
sortModel->setSourceModel(_model);
_ui->_activityList->setModel(sortModel);
- sortModel->setSortRole(ActivityListModel::UnderlyingDataRole);
+ sortModel->setSortRole(Models::UnderlyingDataRole);
_ui->_activityList->hideColumn(static_cast<int>(ActivityListModel::ActivityRole::Path));
- _ui->_activityList->horizontalHeader()->setSectionResizeMode(QHeaderView::ResizeToContents);
+ _ui->_activityList->horizontalHeader()->setSectionResizeMode(QHeaderView::Interactive);
_ui->_activityList->horizontalHeader()->setSectionResizeMode(static_cast<int>(ActivityListModel::ActivityRole::Text), QHeaderView::Stretch);
_ui->_activityList->horizontalHeader()->setSortIndicator(static_cast<int>(ActivityListModel::ActivityRole::PointInTime), Qt::DescendingOrder);
@@ -108,10 +104,6 @@ ActivityWidget::ActivityWidget(QWidget *parent)
}
});
- _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);
-
connect(_model, &QAbstractItemModel::modelReset, this, &ActivityWidget::dataChanged);
connect(_ui->_activityList, &QListView::activated, this, &ActivityWidget::slotOpenFile);
@@ -524,22 +516,18 @@ ActivitySettings::ActivitySettings(QWidget *parent)
hbox->addWidget(_tab);
_activityWidget = new ActivityWidget(this);
_activityTabId = _tab->addTab(_activityWidget, Theme::instance()->applicationIcon(), tr("Server Activity"));
- 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);
@@ -594,39 +582,11 @@ void ActivitySettings::slotShowActivityTab()
}
}
-void ActivitySettings::slotShowIssuesTab(const QString &folderAlias)
+void ActivitySettings::slotShowIssuesTab()
{
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);
- }
-
- QApplication::clipboard()->setText(text);
- emit guiLog(tr("Copied to clipboard"), message);
}
void ActivitySettings::slotRemoveAccount(AccountState *ptr)
diff --git a/src/gui/activitywidget.h b/src/gui/activitywidget.h
index 4aa903c4c..4e85747f3 100644
--- a/src/gui/activitywidget.h
+++ b/src/gui/activitywidget.h
@@ -80,7 +80,6 @@ public slots:
signals:
void guiLog(const QString &, const QString &);
- void copyToClipboard();
void dataChanged();
void hideActivityTab(bool);
void newNotification();
@@ -138,10 +137,9 @@ public slots:
void setNotificationRefreshInterval(std::chrono::milliseconds interval);
- void slotShowIssuesTab(const QString &folderAlias);
+ void slotShowIssuesTab();
private slots:
- void slotCopyToClipboard();
void setActivityTabHidden(bool hidden);
void slotRegularNotificationCheck();
void slotShowIssueItemCount(int cnt);
diff --git a/src/gui/folderman.cpp b/src/gui/folderman.cpp
index f351c59db..520b3b6db 100644
--- a/src/gui/folderman.cpp
+++ b/src/gui/folderman.cpp
@@ -536,9 +536,7 @@ void FolderMan::slotFolderCanSyncChanged()
Folder *FolderMan::folder(const QString &alias)
{
if (!alias.isEmpty()) {
- if (_folderMap.contains(alias)) {
- return _folderMap[alias];
- }
+ return _folderMap.value(alias);
}
return nullptr;
}
diff --git a/src/gui/issueswidget.cpp b/src/gui/issueswidget.cpp
index c9a972beb..e6707ddcd 100644
--- a/src/gui/issueswidget.cpp
+++ b/src/gui/issueswidget.cpp
@@ -24,6 +24,7 @@
#include "folderman.h"
#include "syncfileitem.h"
#include "folder.h"
+#include "models.h"
#include "openfilemanager.h"
#include "protocolwidget.h"
#include "accountstate.h"
@@ -37,18 +38,20 @@
#include <climits>
+namespace {
+bool persistsUntilLocalDiscovery(const OCC::ProtocolItem &data)
+{
+ return data.status() == OCC::SyncFileItem::Conflict
+ || (data.status() == OCC::SyncFileItem::FileIgnored && data.direction() == OCC::SyncFileItem::Up);
+}
+
+}
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)
@@ -61,59 +64,41 @@ IssuesWidget::IssuesWidget(QWidget *parent)
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);
+ this, [this](const QString &folderAlias, const QString &message, ErrorCategory) {
+ auto item = SyncFileItemPtr::create();
+ item->_status = SyncFileItem::NormalError;
+ item->_errorString = message;
+ item->_responseTimeStamp = QDateTime::currentDateTime().toString(Qt::RFC2822Date).toUtf8();
+ _model->addProtocolItem(ProtocolItem { folderAlias, item });
+ });
+ _model = new ProtocolItemModel(this);
+ _sortModel = new QSortFilterProxyModel(this);
+ _sortModel->setSourceModel(_model);
+ _ui->_tableView->setModel(_sortModel);
+ connect(_ui->_tableView, &QTreeView::customContextMenuRequested, this, &IssuesWidget::slotItemContextMenu);
- // Adjust copyToClipboard() when making changes here!
- QStringList header;
- header << tr("Time");
- header << tr("File");
- header << tr("Folder");
- header << tr("Issue");
+ _ui->_tableView->horizontalHeader()->setObjectName(QStringLiteral("ActivityErrorListHeaderV2"));
+ _ui->_tableView->horizontalHeader()->setSectionResizeMode(QHeaderView::Interactive);
+ _ui->_tableView->horizontalHeader()->setSectionResizeMode(static_cast<int>(ProtocolItemModel::ProtocolItemRole::Action), QHeaderView::Stretch);
+ _ui->_tableView->horizontalHeader()->setSortIndicator(static_cast<int>(ProtocolItemModel::ProtocolItemRole::Time), Qt::DescendingOrder);
- int timestampColumnExtra = 0;
-#ifdef Q_OS_WIN
- timestampColumnExtra = 20; // font metrics are broken on Windows, see #4721
-#endif
+ ConfigFile cfg;
+ cfg.restoreGeometryHeader(_ui->_tableView->horizontalHeader());
- _ui->_treeWidget->setHeaderLabels(header);
- _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
+ connect(qApp, &QApplication::aboutToQuit, this, [this] {
+ ConfigFile cfg;
+ cfg.saveGeometryHeader(_ui->_tableView->horizontalHeader());
+ });
- _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); });
+ connect(_model, &ProtocolItemModel::rowsInserted, this, [this] {
+ _ui->_tooManyIssuesWarning->setVisible(_model->isModelFull());
+ });
+ connect(_model, &ProtocolItemModel::modelReset, this, [this] {
+ _ui->_tooManyIssuesWarning->setVisible(_model->isModelFull());
+ });
_ui->_conflictHelp->hide();
_ui->_conflictHelp->setText(
@@ -126,115 +111,6 @@ 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) {
- delete item;
- 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) {
@@ -245,20 +121,23 @@ void IssuesWidget::slotProgressInfo(const QString &folder, const ProgressInfo &p
return;
const auto &engine = f->syncEngine();
const auto style = engine.lastLocalDiscoveryStyle();
- cleanItems([&](QTreeWidgetItem *item) {
- if (ProtocolItem::extraData(item).folderName != folder)
+ _model->remove([&](const ProtocolItem &item) {
+ if (item.folderName() != folder) {
return false;
- if (style == LocalDiscoveryStyle::FilesystemOnly)
+ }
+ if (style == LocalDiscoveryStyle::FilesystemOnly) {
return true;
- if (!persistsUntilLocalDiscovery(item))
+ }
+ if (!persistsUntilLocalDiscovery(item)) {
return true;
-
+ }
// Definitely wipe the entry if the file no longer exists
- if (!QFileInfo(f->path() + ProtocolItem::extraData(item).path).exists())
+ if (!QFileInfo::exists(f->path() + item.path())) {
return true;
+ }
- auto path = QFileInfo(ProtocolItem::extraData(item).path).dir().path();
- if (path == ".")
+ auto path = QFileInfo(item.path()).dir().path();
+ if (path == QLatin1Char('.'))
path.clear();
return engine.shouldDiscoverLocally(path);
@@ -268,13 +147,10 @@ void IssuesWidget::slotProgressInfo(const QString &folder, const ProgressInfo &p
// 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);
+ for (const auto &data : _model->rawData()) {
+ if (data.folderName() == folder
+ && data.status() == SyncFileItem::Conflict) {
+ conflicts.append(data.path());
}
}
emit ProgressDispatcher::instance()->folderConflicts(folder, conflicts);
@@ -287,215 +163,20 @@ void IssuesWidget::slotItemCompleted(const QString &folder, const SyncFileItemPt
{
if (!item->showInIssuesTab())
return;
- QTreeWidgetItem *line = ProtocolItem::create(folder, *item);
- if (!line)
- return;
- addItem(line);
+ _model->addProtocolItem(ProtocolItem { folder, item });
}
-void IssuesWidget::slotRefreshIssues()
+void IssuesWidget::slotItemContextMenu()
{
- 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));
+ auto rows = _ui->_tableView->selectionModel()->selectedRows();
+ for (int i = 0; i < rows.size(); ++i) {
+ rows[i] = _sortModel->mapToSource(rows[i]);
}
-
- _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 != nullptr);
-
- 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->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);
+ ProtocolWidget::showContextMenu(this, _model, rows);
}
+// TODO: needs porting
+#if 0
void IssuesWidget::addErrorWidget(QTreeWidgetItem *item, const QString &message, ErrorCategory category)
{
QWidget *widget = nullptr;
@@ -532,4 +213,5 @@ void IssuesWidget::retryInsufficentRemoteStorageErrors(const QString &folderAlia
folder->journalDb()->wipeErrorBlacklistCategory(SyncJournalErrorBlacklistRecord::InsufficientRemoteStorage);
folderman->scheduleFolderNext(folder);
}
+#endif
}
diff --git a/src/gui/issueswidget.h b/src/gui/issueswidget.h
index c5c2874fd..0f68a7184 100644
--- a/src/gui/issueswidget.h
+++ b/src/gui/issueswidget.h
@@ -20,12 +20,13 @@
#include <QLocale>
#include <QTimer>
+#include "protocolitemmodel.h"
#include "progressdispatcher.h"
#include "owncloudgui.h"
#include "ui_issueswidget.h"
-class QPushButton;
+class QSortFilterProxyModel;
namespace OCC {
class SyncResult;
@@ -46,50 +47,23 @@ public:
explicit IssuesWidget(QWidget *parent = nullptr);
~IssuesWidget() override;
- void storeSyncIssues(QTextStream &ts);
- void showFolderErrors(const QString &folderAlias);
-
public slots:
- void addError(const QString &folderAlias, const QString &message, ErrorCategory category);
+ // 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 *) override;
- void hideEvent(QHideEvent *) override;
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);
+ void slotItemContextMenu();
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;
+ // void retryInsufficentRemoteStorageErrors(const QString &folderAlias);
- /// Optimization: keep track of all folder/paths pairs that have an associated issue
- QSet<QPair<QString, QString>> _pathsWithIssues;
+ ProtocolItemModel *_model;
+ QSortFilterProxyModel *_sortModel;
Ui::IssuesWidget *_ui;
};
diff --git a/src/gui/issueswidget.ui b/src/gui/issueswidget.ui
index 73c0ca5d5..7f6bab062 100644
--- a/src/gui/issueswidget.ui
+++ b/src/gui/issueswidget.ui
@@ -25,110 +25,32 @@
</widget>
</item>
<item>
- <layout class="QHBoxLayout" name="horizontalLayout_3">
- <item>
- <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>&lt;no filter&gt;</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>&lt;no filter&gt;</string>
- </property>
- </item>
- </widget>
- </item>
- </layout>
- </item>
- <item>
- <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">
+ <widget class="QTableView" name="_tableView">
+ <property name="contextMenuPolicy">
+ <enum>Qt::CustomContextMenu</enum>
+ </property>
<property name="alternatingRowColors">
<bool>true</bool>
</property>
- <property name="rootIsDecorated">
+ <property name="selectionBehavior">
+ <enum>QAbstractItemView::SelectRows</enum>
+ </property>
+ <property name="showGrid">
<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>
+ <attribute name="horizontalHeaderShowSortIndicator" stdset="0">
+ <bool>true</bool>
+ </attribute>
+ <attribute name="verticalHeaderVisible">
+ <bool>false</bool>
+ </attribute>
</widget>
</item>
<item>
- <layout class="QHBoxLayout" name="horizontalLayout_2" stretch="1,0">
+ <layout class="QHBoxLayout" name="horizontalLayout_2" stretch="1">
<item>
<layout class="QVBoxLayout" name="verticalLayout_2">
<item>
@@ -156,41 +78,8 @@
</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/>
diff --git a/src/gui/models.cpp b/src/gui/models.cpp
new file mode 100644
index 000000000..2c0c37c5a
--- /dev/null
+++ b/src/gui/models.cpp
@@ -0,0 +1,49 @@
+/*
+ * Copyright (C) by Hannah von Reth <hannah.vonreth@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 "models.h"
+
+#include <QApplication>
+#include <QItemSelectionRange>
+#include <QTextStream>
+
+QString OCC::Models::formatSelection(const QModelIndexList &items)
+{
+ if (items.isEmpty()) {
+ return {};
+ }
+ const auto columns = items.first().model()->columnCount();
+ QString out;
+ QTextStream stream(&out);
+
+ for (int c = 0; c < columns; ++c) {
+ const auto width = items.first().model()->headerData(c, Qt::Horizontal, StringFormatWidthRole).toInt();
+ Q_ASSERT(width);
+ stream << right
+ << qSetFieldWidth(width)
+ << items.first().model()->headerData(c, Qt::Horizontal).toString()
+ << qSetFieldWidth(0) << ",";
+ }
+ stream << endl;
+ for (const auto &index : items) {
+ for (int c = 0; c < columns; ++c) {
+ const auto &child = index.siblingAtColumn(c);
+ stream << right
+ << qSetFieldWidth(child.model()->headerData(c, Qt::Horizontal, StringFormatWidthRole).toInt())
+ << child.data(Qt::DisplayRole).toString()
+ << qSetFieldWidth(0) << ",";
+ }
+ stream << endl;
+ }
+ return out;
+}
diff --git a/src/gui/models.h b/src/gui/models.h
new file mode 100644
index 000000000..8cca50725
--- /dev/null
+++ b/src/gui/models.h
@@ -0,0 +1,35 @@
+/*
+ * Copyright (C) by Hannah von Reth <hannah.vonreth@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.
+ */
+#pragma once
+
+#include <QModelIndexList>
+#include <QString>
+#include <QtGlobal>
+
+namespace OCC {
+
+namespace Models {
+ enum DataRoles {
+ UnderlyingDataRole = Qt::UserRole + 100,
+ StringFormatWidthRole // The width for a cvs formated column
+ };
+
+ /**
+ * Returns a cvs representation of a table
+ */
+ QString formatSelection(const QModelIndexList &items);
+
+
+}
+}
diff --git a/src/gui/protocolitem.cpp b/src/gui/protocolitem.cpp
new file mode 100644
index 000000000..769236005
--- /dev/null
+++ b/src/gui/protocolitem.cpp
@@ -0,0 +1,82 @@
+/*
+ * Copyright (C) by Hannah von Reth <hannah.vonreth@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 "protocolitem.h"
+
+#include "progressdispatcher.h"
+
+#include <QApplication>
+#include <QFileInfo>
+#include <QMenu>
+#include <QPointer>
+
+using namespace OCC;
+
+ProtocolItem::ProtocolItem(const QString &folder, const SyncFileItemPtr &item)
+ : _path(item->destination())
+ , _folderName(folder)
+ , _size(item->_size)
+ , _status(item->_status)
+ , _direction(item->_direction)
+ , _message(item->_errorString)
+ , _sizeIsRelevant(ProgressInfo::isSizeDependent(*item))
+{
+ if (!item->_responseTimeStamp.isEmpty()) {
+ _timestamp = QDateTime::fromString(QString::fromUtf8(item->_responseTimeStamp), Qt::RFC2822Date);
+ } else {
+ _timestamp = QDateTime::currentDateTime();
+ }
+ if (_message.isEmpty()) {
+ _message = Progress::asResultString(*item);
+ }
+}
+
+QString ProtocolItem::path() const
+{
+ return _path;
+}
+
+QString ProtocolItem::folderName() const
+{
+ return _folderName;
+}
+
+QDateTime ProtocolItem::timestamp() const
+{
+ return _timestamp;
+}
+
+qint64 ProtocolItem::size() const
+{
+ return _size;
+}
+
+SyncFileItem::Status ProtocolItem::status() const
+{
+ return _status;
+}
+
+SyncFileItem::Direction ProtocolItem::direction() const
+{
+ return _direction;
+}
+
+QString ProtocolItem::message() const
+{
+ return _message;
+}
+
+bool ProtocolItem::isSizeRelevant() const
+{
+ return _sizeIsRelevant;
+}
diff --git a/src/gui/protocolitem.h b/src/gui/protocolitem.h
new file mode 100644
index 000000000..84ab99150
--- /dev/null
+++ b/src/gui/protocolitem.h
@@ -0,0 +1,65 @@
+/*
+ * Copyright (C) by Hannah von Reth <hannah.vonreth@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.
+ */
+#pragma once
+
+#include "csync/csync.h"
+#include "libsync/syncfileitem.h"
+
+namespace OCC {
+
+class ProtocolItem
+{
+ Q_GADGET
+public:
+ ProtocolItem() = default;
+ explicit ProtocolItem(const QString &folder, const SyncFileItemPtr &item);
+
+ QString path() const;
+
+ QString folderName() const;
+
+ QDateTime timestamp() const;
+
+ qint64 size() const;
+
+ SyncFileItem::Status status() const;
+
+ SyncFileItem::Direction direction() const;
+
+ QString message() const;
+
+ bool isSizeRelevant() const;
+
+private:
+ QString _path;
+ QString _folderName;
+ QDateTime _timestamp;
+ qint64 _size;
+ SyncFileItem::Status _status BITFIELD(4);
+ SyncFileItem::Direction _direction BITFIELD(3);
+
+ QString _message;
+ bool _sizeIsRelevant;
+
+ /**
+ * The creation id
+ */
+ qulonglong _id = [] {
+ static qulonglong count = 0;
+ return ++count;
+ }();
+ friend class ProtocolItemModel;
+};
+
+}
diff --git a/src/gui/protocolitemmodel.cpp b/src/gui/protocolitemmodel.cpp
new file mode 100644
index 000000000..84cf5c4ef
--- /dev/null
+++ b/src/gui/protocolitemmodel.cpp
@@ -0,0 +1,217 @@
+/*
+ * Copyright (C) by Hannah von Reth <hannah.vonreth@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 "protocolitemmodel.h"
+
+#include "account.h"
+#include "accountstate.h"
+#include "models.h"
+#include "gui/folderman.h"
+
+#include "theme.h"
+
+#include <QIcon>
+
+namespace {
+auto getFolder(const OCC::ProtocolItem &item)
+{
+ auto f = OCC::FolderMan::instance()->folder(item.folderName());
+ OC_ASSERT(f);
+ return f;
+}
+}
+
+using namespace OCC;
+
+ProtocolItemModel::ProtocolItemModel(QObject *parent, bool issueMode, int maxLogSize)
+ : QAbstractTableModel(parent)
+ , _issueMode(issueMode)
+ , _maxLogSize(maxLogSize)
+{
+ _data.reserve(maxLogSize);
+}
+
+int ProtocolItemModel::rowCount(const QModelIndex &parent) const
+{
+ Q_ASSERT(checkIndex(parent));
+ if (parent.isValid()) {
+ return 0;
+ }
+ return actualSize();
+}
+
+int ProtocolItemModel::columnCount(const QModelIndex &parent) const
+{
+ Q_ASSERT(checkIndex(parent));
+ if (parent.isValid()) {
+ return 0;
+ }
+ return static_cast<int>(ProtocolItemRole::ColumnCount);
+}
+
+QVariant ProtocolItemModel::data(const QModelIndex &index, int role) const
+{
+ Q_ASSERT(checkIndex(index, QAbstractItemModel::CheckIndexOption::IndexIsValid));
+
+ const auto column = static_cast<ProtocolItemRole>(index.column());
+ const auto &item = protocolItem(index);
+ switch (role) {
+ case Qt::DisplayRole:
+ switch (column) {
+ case ProtocolItemRole::Time:
+ return item.timestamp();
+ case ProtocolItemRole::Folder:
+ return getFolder(item)->shortGuiLocalPath();
+ case ProtocolItemRole::Action:
+ return item.message();
+ case ProtocolItemRole::Size:
+ return item.isSizeRelevant() ? Utility::octetsToString(item.size()) : QVariant();
+ case ProtocolItemRole::File:
+ return Utility::fileNameForGuiUse(item.path());
+ case ProtocolItemRole::Account:
+ return getFolder(item)->accountState()->account()->displayName();
+ case ProtocolItemRole::ColumnCount:
+ Q_UNREACHABLE();
+ break;
+ }
+ case Qt::DecorationRole:
+ if (column == ProtocolItemRole::Action) {
+ const auto status = item.status();
+ if (status == SyncFileItem::NormalError
+ || status == SyncFileItem::FatalError
+ || status == SyncFileItem::DetailError
+ || status == SyncFileItem::BlacklistedError) {
+ return Theme::instance()->syncStateIcon(SyncResult::Error);
+ } else if (Progress::isWarningKind(status)) {
+ return Theme::instance()->syncStateIcon(SyncResult::Problem);
+ } else {
+ return {};
+ // TODO: display icon on success?
+ // return Theme::instance()->syncStateIcon(SyncResult::Success);
+ }
+ }
+ case Models::UnderlyingDataRole:
+ switch (column) {
+ case ProtocolItemRole::Time:
+ return item.timestamp();
+ case ProtocolItemRole::Folder:
+ return item.folderName();
+ case ProtocolItemRole::Action:
+ return item.message();
+ case ProtocolItemRole::Size:
+ return item.size();
+ case ProtocolItemRole::File:
+ return item.path();
+ case ProtocolItemRole::Account:
+ return getFolder(item)->accountState()->account()->displayName();
+ case ProtocolItemRole::ColumnCount:
+ Q_UNREACHABLE();
+ break;
+ }
+ }
+ return {};
+}
+
+QVariant ProtocolItemModel::headerData(int section, Qt::Orientation orientation, int role) const
+{
+ if (orientation == Qt::Horizontal) {
+ const auto actionRole = static_cast<ProtocolItemRole>(section);
+ switch (role) {
+ case Qt::DisplayRole:
+ switch (actionRole) {
+ case ProtocolItemRole::Time:
+ return tr("Time");
+ case ProtocolItemRole::File:
+ return tr("File");
+ case ProtocolItemRole::Folder:
+ return tr("Folder");
+ case ProtocolItemRole::Action:
+ return _issueMode ? tr("Issues") : tr("Action");
+ case ProtocolItemRole::Size:
+ return tr("Size");
+ case ProtocolItemRole::Account:
+ return tr("Account");
+ case ProtocolItemRole::ColumnCount:
+ Q_UNREACHABLE();
+ break;
+ };
+
+ case Models::StringFormatWidthRole:
+ // TODO: fine tune
+ switch (actionRole) {
+ case ProtocolItemRole::Time:
+ return 20;
+ case ProtocolItemRole::Folder:
+ return 30;
+ case ProtocolItemRole::Action:
+ return 15;
+ case ProtocolItemRole::Size:
+ return 6;
+ case ProtocolItemRole::File:
+ return 64;
+ case ProtocolItemRole::Account:
+ return 20;
+ case ProtocolItemRole::ColumnCount:
+ Q_UNREACHABLE();
+ break;
+ }
+ };
+ }
+ return QAbstractTableModel::headerData(section, orientation, role);
+}
+
+void ProtocolItemModel::addProtocolItem(const ProtocolItem &&item)
+{
+ Q_ASSERT(actualSize() == _data.size());
+ if (_data.size() >= _maxLogSize) {
+ beginRemoveRows(QModelIndex(), 0, 0);
+ _start++;
+ endRemoveRows();
+ } else {
+ _data.push_back({});
+ }
+ // _data.size() might differ
+ const auto size = actualSize();
+ beginInsertRows(QModelIndex(), size, size);
+ _data[convertToIndex(size)] = std::move(item);
+ _end++;
+ endInsertRows();
+}
+
+const ProtocolItem &ProtocolItemModel::protocolItem(const QModelIndex &index) const
+{
+ Q_ASSERT(checkIndex(index, QAbstractItemModel::CheckIndexOption::IndexIsValid));
+ return _data.at(convertToIndex(index.row()));
+}
+
+const std::vector<ProtocolItem> &ProtocolItemModel::rawData() const
+{
+ return _data;
+}
+
+void ProtocolItemModel::remove(const std::function<bool(const ProtocolItem &)> &filter)
+{
+ if (_data.empty()) {
+ return;
+ }
+ const auto first = protocolItem(index(0, 0));
+ beginResetModel();
+ _data.erase(std::remove_if(_data.begin(), _data.end(), filter), _data.end());
+ // find start again
+ _start = std::distance(_data.cbegin(), std::find_if(_data.cbegin(), _data.cend(), [&first](const ProtocolItem &pi) {
+ return pi._id <= first._id;
+ }));
+ _end = _start + _data.size();
+ endResetModel();
+ _data.reserve(_maxLogSize);
+}
diff --git a/src/gui/protocolitemmodel.h b/src/gui/protocolitemmodel.h
new file mode 100644
index 000000000..c2ea8be15
--- /dev/null
+++ b/src/gui/protocolitemmodel.h
@@ -0,0 +1,77 @@
+/*
+ * Copyright (C) by Hannah von Reth <hannah.vonreth@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.
+ */
+#pragma once
+
+#include <QAbstractTableModel>
+
+#include "protocolitem.h"
+
+
+namespace OCC {
+class ProtocolItemModel : public QAbstractTableModel
+{
+ Q_OBJECT
+public:
+ enum class ProtocolItemRole {
+ Action,
+ File,
+ Folder,
+ Size,
+ Account,
+ Time,
+
+ ColumnCount
+ };
+ /**
+ * @brief ProtocolItemModel
+ * @param parent
+ * @param issueMode Whether we are tracking all synced items or issues
+ */
+ ProtocolItemModel(QObject *parent = nullptr, bool issueMode = false, int maxLogSize=2000);
+
+ int rowCount(const QModelIndex &parent = {}) const override;
+ int columnCount(const QModelIndex &parent = {}) const override;
+ QVariant data(const QModelIndex &index, int role) const override;
+ QVariant headerData(int section, Qt::Orientation orientation, int role) const override;
+
+ void addProtocolItem(const ProtocolItem &&item);
+ const ProtocolItem &protocolItem(const QModelIndex &index) const;
+
+ const std::vector<ProtocolItem> &rawData() const;
+ void remove(const std::function<bool(const ProtocolItem &)> &filter);
+
+ bool isModelFull() const
+ {
+ return _data.size() == _maxLogSize;
+ }
+
+private:
+ bool _issueMode;
+ int _maxLogSize;
+ std::vector<ProtocolItem> _data;
+
+ qulonglong _start = 0;
+ qulonglong _end = 0;
+
+ constexpr qulonglong actualSize() const
+ {
+ return _end - _start;
+ }
+ constexpr int convertToIndex(qulonglong i) const
+ {
+ return (i + _start) % _maxLogSize;
+ }
+};
+
+}
diff --git a/src/gui/protocolwidget.cpp b/src/gui/protocolwidget.cpp
index 4d962d2e5..dc7e3fe62 100644
--- a/src/gui/protocolwidget.cpp
+++ b/src/gui/protocolwidget.cpp
@@ -12,166 +12,32 @@
* for more details.
*/
+#include <QCursor>
#include <QtGui>
#include <QtWidgets>
+#include "activitylistmodel.h"
#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 "models.h"
#include "openfilemanager.h"
#include "guiutility.h"
#include "accountstate.h"
+#include "syncfileitem.h"
+#include "activitylistmodel.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 &folderName, const SyncFileItem &item)
-{
- auto folder = FolderMan::instance()->folder(folderName);
-
- 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 << (folder ? folder->shortGuiLocalPath() : QDir::toNativeSeparators(folderName));
-
- // If the error string is set, it's prefered because it is a useful user message.
- QString message = item._errorString;
- if (message.isEmpty()) {
- message = item._messageString;
- }
- 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->setIcon(0, icon);
- twitem->setToolTip(0, longTimeStr);
- twitem->setToolTip(1, item.destination());
- twitem->setToolTip(3, message);
- ProtocolItem::ExtraData data;
- data.timestamp = timestamp;
- data.path = item.destination();
- data.folderName = folderName;
- 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, 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)
@@ -181,37 +47,28 @@ ProtocolWidget::ProtocolWidget(QWidget *parent)
connect(ProgressDispatcher::instance(), &ProgressDispatcher::itemCompleted,
this, &ProtocolWidget::slotItemCompleted);
- connect(_ui->_treeWidget, &QTreeWidget::itemActivated, this, &ProtocolWidget::slotOpenFile);
+ connect(_ui->_tableView, &QTreeWidget::customContextMenuRequested, this, &ProtocolWidget::slotItemContextMenu);
- _ui->_treeWidget->setContextMenuPolicy(Qt::CustomContextMenu);
- connect(_ui->_treeWidget, &QTreeWidget::customContextMenuRequested, this, &ProtocolWidget::slotItemContextMenu);
+ _model = new ProtocolItemModel(this);
+ _sortModel = new QSortFilterProxyModel(this);
+ _sortModel->setSourceModel(_model);
+ _sortModel->setSortRole(Models::UnderlyingDataRole);
+ _ui->_tableView->setModel(_sortModel);
- // Adjust copyToClipboard() when making changes here!
- QStringList header;
- header << tr("Time");
- header << tr("File");
- header << tr("Folder");
- header << tr("Action");
- header << tr("Size");
+ _ui->_tableView->horizontalHeader()->setSectionResizeMode(QHeaderView::Interactive);
+ _ui->_tableView->horizontalHeader()->setSectionResizeMode(static_cast<int>(ProtocolItemModel::ProtocolItemRole::File), QHeaderView::Stretch);
+ _ui->_tableView->horizontalHeader()->setSortIndicator(static_cast<int>(ProtocolItemModel::ProtocolItemRole::Time), Qt::DescendingOrder);
- _ui->_treeWidget->setHeaderLabels(header);
- int timestampColumnWidth =
- _ui->_treeWidget->fontMetrics().boundingRect(ProtocolItem::timeString(QDateTime::currentDateTime())).width();
- _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"));
+ _ui->_tableView->horizontalHeader()->setObjectName(QStringLiteral("ActivityListHeaderV2"));
+ ConfigFile cfg;
+ cfg.restoreGeometryHeader(_ui->_tableView->horizontalHeader());
+
+ connect(qApp, &QApplication::aboutToQuit, this, [this] {
+ ConfigFile cfg;
+ cfg.saveGeometryHeader(_ui->_tableView->horizontalHeader());
+ });
- 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);
+ _ui->_headerLabel->setText(tr("Local sync protocol"));
}
ProtocolWidget::~ProtocolWidget()
@@ -219,104 +76,62 @@ 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)
+void ProtocolWidget::showContextMenu(QWidget *parent, ProtocolItemModel *model, const QModelIndexList &items)
{
- ConfigFile cfg;
- cfg.saveGeometryHeader(_ui->_treeWidget->header());
- QWidget::hideEvent(ev);
-}
+ auto menu = new QMenu(parent);
+ menu->setAttribute(Qt::WA_DeleteOnClose);
-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);
+ menu->addAction(tr("Copy to clipboard"), parent, [text = Models::formatSelection(items)] {
+ QApplication::clipboard()->setText(text);
+ });
+
+ if (items.size() == 1) {
+ const auto &data = model->protocolItem(items.first());
+ auto folder = FolderMan::instance()->folder(data.folderName());
+ OC_ASSERT(folder);
+ if (!folder)
+ return;
+
+ {
+ const QString localPath = folder->path() + data.path();
+ if (QFileInfo::exists(localPath)) {
+ menu->addAction(tr("Show in file browser"), parent, [localPath] {
+ if (QFileInfo::exists(localPath)) {
+ showInFileManager(localPath);
+ }
+ });
+ }
+ }
+ // "Open in Browser" action
+ {
+ fetchPrivateLinkUrl(folder->accountState()->account(), folder->remotePathTrailingSlash() + data.path(), parent, [parent, menu = QPointer<QMenu>(menu)](const QString &url) {
+ // as fetchPrivateLinkUrl is async we need to check the menu still exists
+ if (menu) {
+ menu->addAction(tr("Show in web browser"), parent, [url, parent] {
+ Utility::openBrowser(url, parent);
+ });
+ }
+ });
+ }
+ }
+ menu->popup(QCursor::pos());
}
-void ProtocolWidget::slotOpenFile(QTreeWidgetItem *item, int)
+void ProtocolWidget::slotItemContextMenu()
{
- 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);
- }
+ QModelIndexList list;
+ auto rows = _ui->_tableView->selectionModel()->selectedRows();
+ for (int i = 0; i < rows.size(); ++i) {
+ rows[i] = _sortModel->mapToSource(rows[i]);
}
+ showContextMenu(this, _model, rows);
}
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;
- }
+ _model->addProtocolItem(ProtocolItem { folder, item });
}
}
diff --git a/src/gui/protocolwidget.h b/src/gui/protocolwidget.h
index 79ff35868..da3de2c60 100644
--- a/src/gui/protocolwidget.h
+++ b/src/gui/protocolwidget.h
@@ -21,13 +21,15 @@
#include "progressdispatcher.h"
#include "owncloudgui.h"
+#include "protocolitemmodel.h"
+#include "protocolitem.h"
#include "ui_protocolwidget.h"
class QPushButton;
+class QSortFilterProxyModel;
namespace OCC {
-class SyncResult;
namespace Ui {
class ProtocolWidget;
@@ -35,49 +37,6 @@ namespace Ui {
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;
- qint64 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
*/
@@ -88,23 +47,18 @@ public:
explicit ProtocolWidget(QWidget *parent = nullptr);
~ProtocolWidget() override;
- void storeSyncActivity(QTextStream &ts);
+ static void showContextMenu(QWidget *parent, ProtocolItemModel *model, const QModelIndexList &items);
+
public slots:
void slotItemCompleted(const QString &folder, const SyncFileItemPtr &item);
- void slotOpenFile(QTreeWidgetItem *item, int);
-
-protected:
- void showEvent(QShowEvent *) override;
- void hideEvent(QHideEvent *) override;
private slots:
- void slotItemContextMenu(const QPoint &pos);
-
-signals:
- void copyToClipboard();
+ void slotItemContextMenu();
private:
+ ProtocolItemModel *_model;
+ QSortFilterProxyModel *_sortModel;
Ui::ProtocolWidget *_ui;
};
}
diff --git a/src/gui/protocolwidget.ui b/src/gui/protocolwidget.ui
index 3867ec7a9..7941d7418 100644
--- a/src/gui/protocolwidget.ui
+++ b/src/gui/protocolwidget.ui
@@ -25,42 +25,28 @@
</widget>
</item>
<item>
- <widget class="QTreeWidget" name="_treeWidget">
+ <widget class="QTableView" name="_tableView">
+ <property name="contextMenuPolicy">
+ <enum>Qt::CustomContextMenu</enum>
+ </property>
<property name="alternatingRowColors">
<bool>true</bool>
</property>
- <property name="rootIsDecorated">
- <bool>false</bool>
+ <property name="selectionBehavior">
+ <enum>QAbstractItemView::SelectRows</enum>
</property>
- <property name="uniformRowHeights">
- <bool>true</bool>
+ <property name="showGrid">
+ <bool>false</bool>
</property>
<property name="sortingEnabled">
<bool>true</bool>
</property>
- <property name="columnCount">
- <number>4</number>
+ <property name="wordWrap">
+ <bool>false</bool>
</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>
+ <attribute name="verticalHeaderVisible">
+ <bool>false</bool>
+ </attribute>
</widget>
</item>
<item>
diff --git a/src/gui/settingsdialog.cpp b/src/gui/settingsdialog.cpp
index c2bf31357..4402d9576 100644
--- a/src/gui/settingsdialog.cpp
+++ b/src/gui/settingsdialog.cpp
@@ -327,12 +327,12 @@ void SettingsDialog::showActivityPage()
}
}
-void SettingsDialog::showIssuesList(const QString &folderAlias)
+void SettingsDialog::showIssuesList()
{
if (!_activityAction)
return;
_activityAction->trigger();
- _activitySettings->slotShowIssuesTab(folderAlias);
+ _activitySettings->slotShowIssuesTab();
}
void SettingsDialog::accountAdded(AccountState *s)
diff --git a/src/gui/settingsdialog.h b/src/gui/settingsdialog.h
index a9fdd4239..f977ffe6d 100644
--- a/src/gui/settingsdialog.h
+++ b/src/gui/settingsdialog.h
@@ -59,7 +59,7 @@ public:
public slots:
void showFirstPage();
void showActivityPage();
- void showIssuesList(const QString &folderAlias);
+ void showIssuesList();
void slotSwitchPage(QAction *action);
void slotRefreshActivity(AccountState *accountState);
void slotRefreshActivityAccountStateSender();
diff --git a/test/modeltests/CMakeLists.txt b/test/modeltests/CMakeLists.txt
index c7f4bc693..e2e5d5934 100644
--- a/test/modeltests/CMakeLists.txt
+++ b/test/modeltests/CMakeLists.txt
@@ -1 +1,2 @@
owncloud_add_test(ActivityModel)
+owncloud_add_test(ProtocolModel)
diff --git a/test/modeltests/testactivitymodel.cpp b/test/modeltests/testactivitymodel.cpp
index 63cec088b..066a9c9b8 100644
--- a/test/modeltests/testactivitymodel.cpp
+++ b/test/modeltests/testactivitymodel.cpp
@@ -23,7 +23,7 @@ private Q_SLOTS:
{
auto model = new ActivityListModel(this);
- auto tester = new QAbstractItemModelTester(model, this);
+ new QAbstractItemModelTester(model, this);
auto manager = AccountManager::instance();
diff --git a/test/modeltests/testprotocolmodel.cpp b/test/modeltests/testprotocolmodel.cpp
new file mode 100644
index 000000000..2462827cc
--- /dev/null
+++ b/test/modeltests/testprotocolmodel.cpp
@@ -0,0 +1,48 @@
+
+/*
+ * This software is in the public domain, furnished "as is", without technical
+ * support, and with no warranty, express or implied, as to its usefulness for
+ * any purpose.
+ *
+ */
+
+#include "gui/protocolitemmodel.h"
+
+#include <QTest>
+#include <QAbstractItemModelTester>
+
+namespace OCC {
+
+class TestProtocolModel : public QObject
+{
+ Q_OBJECT
+
+private Q_SLOTS:
+ void testInsertAndRemove()
+ {
+ // no need to test with 20000 lines
+ const auto TestBacklogSize = 111;
+ auto model = new ProtocolItemModel(this, false, TestBacklogSize);
+
+ new QAbstractItemModelTester(model, this);
+
+ // populate with dummy data
+ auto item = SyncFileItemPtr::create();
+ for (int i = 0; i < TestBacklogSize * 1.1; ++i) {
+ model->addProtocolItem({ ProtocolItem(QStringLiteral("foo") + QString::number(i), item) });
+ }
+
+ const auto oldSize = model->rowCount();
+ QCOMPARE(oldSize, TestBacklogSize);
+ // pick one from the middle
+ const auto toBeRemoved = model->protocolItem(model->index(TestBacklogSize / 2, 0));
+ model->remove([&toBeRemoved](const ProtocolItem &pi) {
+ return pi.folderName() == toBeRemoved.folderName();
+ });
+ QCOMPARE(oldSize - 1, model->rowCount());
+ }
+};
+}
+
+QTEST_GUILESS_MAIN(OCC::TestProtocolModel)
+#include "testprotocolmodel.moc"