Show sync errors in a dialog (#855)

* Show sync errors in a dialog.

* Fixed a compile warning.

* Improve windows bintray description.

* Improve sync errors dialog ui.

* Commented out debug code.

* Add dialog header to sync errors dialog.

TODO: There are lots of common logic used in FileBrowserDialog and
SyncErrorsDialog. We should refactor out a base dialog class.

* Load last 50 errors by default.

* Fixed the styles of sync errors dialog.

* Updated sync error messages.

* Double click to open the problematic repo.

And improved dialog header style.

* Ignore close event since we always use one instance of the dialog.

* Only update the model when errors are changed.
This commit is contained in:
Shuai Lin 2017-01-17 03:37:28 +00:00 committed by GitHub
parent 8b51ee600d
commit eb8f79ce96
15 changed files with 813 additions and 5 deletions

View file

@ -230,6 +230,7 @@ SET(moc_headers
src/ui/event-details-dialog.h
src/ui/event-details-tree.h
src/ui/set-repo-password-dialog.h
src/ui/sync-errors-dialog.h
src/filebrowser/file-browser-manager.h
src/filebrowser/file-browser-dialog.h
src/filebrowser/file-browser-requests.h
@ -368,6 +369,7 @@ SET(seafile_client_sources
src/rpc/rpc-client.cpp
src/rpc/local-repo.cpp
src/rpc/clone-task.cpp
src/rpc/sync-error.cpp
src/ui/main-window.cpp
src/ui/init-seafile-dialog.cpp
src/ui/login-dialog.cpp
@ -410,6 +412,7 @@ SET(seafile_client_sources
src/ui/event-details-dialog.cpp
src/ui/event-details-tree.cpp
src/ui/set-repo-password-dialog.cpp
src/ui/sync-errors-dialog.cpp
src/filebrowser/file-browser-manager.cpp
src/filebrowser/file-browser-dialog.cpp
src/filebrowser/file-browser-requests.cpp

View file

@ -52,3 +52,8 @@ FileBrowserDialog QToolBar QToolButton#backwardButton {
FileBrowserDialog QToolBar QToolButton#forwardButton {
margin-left: 0px;
}
SyncErrorsDialog QWidget#mainWidget {
border : 0;
border-radius: 0px;
}

View file

@ -13,3 +13,7 @@ RepoTreeView {
FileTableView QHeaderView::section:first {
padding-left: 36px;
}
SyncErrorsTableView::item {
padding-left: 2px;
}

71
qt.css
View file

@ -618,3 +618,74 @@ SharedItemsTableView QHeaderView::section {
padding-top: 10px;
font-family: Arial;
}
SyncErrorsDialog {
margin: 0;
min-width: 700px;
min-height: 300px;
}
SyncErrorsDialog QSizeGrip {
margin-right: 2px;
}
SyncErrorsDialog QWidget#mainWidget {
border: 1px solid #333;
border-radius: 5px;
background-color: #f2f2f2;
}
SyncErrorsDialog QWidget#mHeader {
min-height: 25px;
max-height: 35px;
margin-left: 10px;
margin-right: 10px;
padding-top: 2px;
padding-bottom: 5px;
/* border: 1px solid red; */
}
SyncErrorsDialog QWidget#mHeader QLabel {
padding-left: 60px;
margin-left: 4px;
}
SyncErrorsDialog QWidget#mHeader QPushButton {
margin-left: 4px;
}
SyncErrorsTableView {
border: 0;
margin: 0;
padding-left: 14px;
padding-right: 14px;
border-bottom: 1px solid #e0e0e0;
background-color: white;
qproperty-focusPolicy: NoFocus;
}
SyncErrorsTableView QHeaderView::section {
border: 0;
padding: 8px;
padding-left: 5px;
outline: 0px;
color: #b3b3b3;
background-color: white;
font-size: 12px;
padding-top: 20px;
}
SyncErrorsTableView::item {
padding-top: 5px;
padding-left: 0px;
border-top: 1px solid #e0e0e0;
}
SyncErrorsTableView::item :last {
padding-right: 13px;
}
SyncErrorsTableView::item:selected {
background-color: #F9E0C7;
}

View file

@ -23,7 +23,7 @@ BEGIN
BLOCK "040904b0"
BEGIN
VALUE "CompanyName", "Seafile Ltd.\0"
VALUE "FileDescription", "The seafile client executable\0"
VALUE "FileDescription", "Seafile Client"
VALUE "FileVersion", RC_VERSION_STR
VALUE "InternalName", "seafile-applet\0"
VALUE "OriginalFilename", "seafile-applet.exe\0"

View file

@ -20,6 +20,7 @@ extern "C" {
#include "utils/utils.h"
#include "local-repo.h"
#include "clone-task.h"
#include "sync-error.h"
#include "api/commit-details.h"
#include "rpc-client.h"
@ -1035,3 +1036,24 @@ bool SeafileRpcClient::getCommitDiff(const QString& repo_id,
g_list_free (objlist);
return true;
}
bool SeafileRpcClient::getSyncErrors(std::vector<SyncError> *errors, int offset, int limit)
{
GError *error = NULL;
GList *objlist = searpc_client_call__objlist(
seafile_rpc_client_,
"seafile_get_file_sync_errors",
SEAFILE_TYPE_FILE_SYNC_ERROR,
&error, 2, "int", offset, "int", limit);
for (GList *ptr = objlist; ptr; ptr = ptr->next) {
SyncError error = SyncError::fromGObject((GObject *)ptr->data);
errors->push_back(error);
}
g_list_foreach (objlist, (GFunc)g_object_unref, NULL);
g_list_free (objlist);
return true;
}

View file

@ -17,6 +17,7 @@ struct _CcnetClient;
class LocalRepo;
class CloneTask;
class SyncError;
class CommitDetails;
class SeafileRpcClient : public QObject {
@ -129,6 +130,8 @@ public:
const QString& previous_commit_id,
CommitDetails *details);
bool getSyncErrors(std::vector<SyncError> *errors, int offset=0, int limit=10);
private:
Q_DISABLE_COPY(SeafileRpcClient)

82
src/rpc/sync-error.cpp Normal file
View file

@ -0,0 +1,82 @@
#include <QObject>
#include <QStringList>
#include <glib-object.h>
#include "utils/utils.h"
#include "sync-error.h"
SyncError SyncError::fromGObject(GObject *obj)
{
SyncError error;
char *repo_id = NULL;
char *repo_name = NULL;
char *path = NULL;
int error_id = 0;
qint64 timestamp = 0;
g_object_get (obj,
"repo_id", &repo_id,
"repo_name", &repo_name,
"path", &path,
"err_id", &error_id,
"timestamp", &timestamp,
NULL);
error.repo_id = repo_id;
error.repo_name = QString::fromUtf8(repo_name);
error.path = QString::fromUtf8(path);
error.error_id = error_id;
error.timestamp = timestamp;
g_free (repo_id);
g_free (repo_name);
g_free (path);
error.translateErrorStr();
return error;
}
// Copied from seafile/daemon/repo-mgr.h
#define SYNC_ERROR_ID_FILE_LOCKED_BY_APP 0
#define SYNC_ERROR_ID_FOLDER_LOCKED_BY_APP 1
#define SYNC_ERROR_ID_FILE_LOCKED 2
#define SYNC_ERROR_ID_INVALID_PATH 3
#define SYNC_ERROR_ID_INDEX_ERROR 4
#define SYNC_ERROR_ID_PATH_END_SPACE_PERIOD 5
#define SYNC_ERROR_ID_PATH_INVALID_CHARACTER 6
void SyncError::translateErrorStr()
{
readable_time_stamp = translateCommitTime(timestamp);
switch (error_id) {
case SYNC_ERROR_ID_FILE_LOCKED_BY_APP:
error_str = QObject::tr("File is locked by another application");
break;
case SYNC_ERROR_ID_FOLDER_LOCKED_BY_APP:
error_str = QObject::tr("Folder is locked by another application");
break;
case SYNC_ERROR_ID_FILE_LOCKED:
error_str = QObject::tr("File is locked by another user");
break;
case SYNC_ERROR_ID_INVALID_PATH:
error_str = QObject::tr("Path is invalid");
break;
case SYNC_ERROR_ID_INDEX_ERROR:
error_str = QObject::tr("Error when indexing");
break;
case SYNC_ERROR_ID_PATH_END_SPACE_PERIOD:
error_str = QObject::tr("Path ends with space or period character");
break;
case SYNC_ERROR_ID_PATH_INVALID_CHARACTER:
error_str = QObject::tr("Path contains invalid characters like '|' or ':'");
break;
default:
// unreachable
qWarning("unknown sync error id %d", error_id);
error_str = "";
}
}

38
src/rpc/sync-error.h Normal file
View file

@ -0,0 +1,38 @@
#ifndef SEAFILE_CLIENT_RPC_SYNC_ERROR_H
#define SEAFILE_CLIENT_RPC_SYNC_ERROR_H
#include <QString>
#include <QMetaType>
struct _GObject;
class SyncError {
public:
QString repo_id;
QString repo_name;
QString path;
qint64 timestamp;
int error_id;
// Generated fields.
QString readable_time_stamp;
QString error_str;
static SyncError fromGObject(_GObject *obj);
void translateErrorStr();
bool operator==(const SyncError& rhs) const {
return repo_id == rhs.repo_id
&& repo_name == rhs.repo_name
&& path == rhs.path
&& error_id == rhs.error_id
&& timestamp == rhs.timestamp;
}
bool operator!=(const SyncError& rhs) const {
return !(*this == rhs);
}
};
#endif // SEAFILE_CLIENT_RPC_SYNC_ERROR_H

View file

@ -437,8 +437,13 @@ void RepoTreeView::createActions()
connect(share_repo_to_group_action_, SIGNAL(triggered()), this, SLOT(shareRepoToGroup()));
// In German translation there is a "seafile" string, so need to use tr("..").arg(..) here
open_in_filebrowser_action_ = new QAction(tr("&Open cloud file browser").arg(getBrand()), this);
QString open_action_text = tr("&Open cloud file browser");
if (open_action_text.contains("%")) {
// In German translation there is a "seafile" string, so need to use tr("..").arg(..) here
open_action_text = open_action_text.arg(getBrand());
}
open_in_filebrowser_action_ = new QAction(open_action_text, this);
open_in_filebrowser_action_->setIcon(QIcon(":/images/cloud-gray.png"));
open_in_filebrowser_action_->setStatusTip(tr("open this library in embedded Cloud File Browser"));
open_in_filebrowser_action_->setIconVisibleInMenu(true);

View file

@ -0,0 +1,437 @@
#include <QtGlobal>
#include <QtWidgets>
#include <QTableView>
#include <QResizeEvent>
#include <QTimer>
#include <QDesktopServices>
#include <QCloseEvent>
#include "QtAwesome.h"
#include "utils/utils.h"
#include "seafile-applet.h"
#include "rpc/rpc-client.h"
#include "rpc/sync-error.h"
#include "rpc/local-repo.h"
#include "sync-errors-dialog.h"
namespace {
const int kUpdateErrorsIntervalMSecs = 3000;
const int kDefaultColumnWidth = 120;
const int kDefaultColumnHeight = 40;
const int kRepoNameColumnWidth = 100;
const int kPathColumnWidth = 150;
const int kErrorColumnWidth = 200;
const int kTimestampColumnWidth = 80;
const int kExtraPadding = 80;
const int kDefaultColumnSum = kRepoNameColumnWidth + kPathColumnWidth + kErrorColumnWidth + kTimestampColumnWidth + kExtraPadding;
enum {
INDEX_EMPTY_VIEW = 0,
INDEX_TABE_VIEW
};
enum {
COLUMN_REPO_NAME = 0,
COLUMN_PATH,
COLUMN_ERROR_STR,
COLUMN_TIMESTAMP,
MAX_COLUMN,
};
} // namespace
// TODO: There are lots of common logic used in FileBrowserDialog and
// SyncErrorsDialog. We should refactor out a base dialog class.
SyncErrorsDialog::SyncErrorsDialog(QWidget *parent)
: QDialog(parent)
{
// setupUi(this);
setWindowTitle(tr("File Sync Errors"));
setWindowIcon(QIcon(":/images/seafile.png"));
setWindowFlags((windowFlags() & ~Qt::WindowContextHelpButtonHint & ~Qt::Dialog)
#if !defined(Q_OS_MAC)
| Qt::FramelessWindowHint
#endif
| Qt::Window);
resizer_ = new QSizeGrip(this);
resizer_->resize(resizer_->sizeHint());
setAttribute(Qt::WA_TranslucentBackground, true);
createTitleBar();
createEmptyView();
table_ = new SyncErrorsTableView;
model_ = new SyncErrorsTableModel(this);
table_->setModel(model_);
QWidget* widget = new QWidget;
widget->setObjectName("mainWidget");
QVBoxLayout* layout = new QVBoxLayout;
layout->setContentsMargins(0, 0, 0, 0);
layout->setSpacing(0);
setLayout(layout);
layout->addWidget(widget);
QVBoxLayout *vlayout = new QVBoxLayout;
vlayout->setContentsMargins(1, 0, 1, 0);
vlayout->setSpacing(0);
widget->setLayout(vlayout);
stack_ = new QStackedWidget;
stack_->insertWidget(INDEX_EMPTY_VIEW, empty_view_);
stack_->insertWidget(INDEX_TABE_VIEW, table_);
stack_->setContentsMargins(0, 0, 0, 0);
vlayout->addWidget(header_);
vlayout->addWidget(stack_);
#ifdef Q_OS_MAC
header_->setVisible(false);
#endif
onModelReset();
connect(model_, SIGNAL(modelReset()), this, SLOT(onModelReset()));
}
void SyncErrorsDialog::createTitleBar()
{
header_ = new QWidget;
header_->setObjectName("mHeader");
QHBoxLayout *layout = new QHBoxLayout;
layout->setContentsMargins(1, 1, 1, 1);
layout->setSpacing(0);
header_->setLayout(layout);
QSpacerItem *spacer1 = new QSpacerItem(40, 20, QSizePolicy::Expanding, QSizePolicy::Minimum);
layout->addSpacerItem(spacer1);
brand_label_ = new QLabel(windowTitle());
brand_label_->setObjectName("mBrand");
layout->addWidget(brand_label_);
QSpacerItem *spacer2 = new QSpacerItem(40, 20, QSizePolicy::Expanding, QSizePolicy::Minimum);
layout->addSpacerItem(spacer2);
minimize_button_ = new QPushButton;
minimize_button_->setObjectName("mMinimizeBtn");
minimize_button_->setToolTip(tr("Minimize"));
minimize_button_->setIcon(awesome->icon(icon_minus, QColor("#808081")));
layout->addWidget(minimize_button_);
connect(minimize_button_, SIGNAL(clicked()), this, SLOT(showMinimized()));
close_button_ = new QPushButton;
close_button_->setObjectName("mCloseBtn");
close_button_->setToolTip(tr("Close"));
close_button_->setIcon(awesome->icon(icon_remove, QColor("#808081")));
layout->addWidget(close_button_);
connect(close_button_, SIGNAL(clicked()), this, SLOT(close()));
header_->installEventFilter(this);
}
bool SyncErrorsDialog::eventFilter(QObject *obj, QEvent *event)
{
if (obj == header_) {
if (event->type() == QEvent::MouseButtonPress) {
QMouseEvent *ev = (QMouseEvent *)event;
QRect frame_rect = frameGeometry();
old_pos_ = ev->globalPos() - frame_rect.topLeft();
return true;
} else if (event->type() == QEvent::MouseMove) {
QMouseEvent *ev = (QMouseEvent *)event;
move(ev->globalPos() - old_pos_);
return true;
}
}
return QDialog::eventFilter(obj, event);
}
void SyncErrorsDialog::closeEvent(QCloseEvent *event)
{
event->ignore();
this->hide();
}
void SyncErrorsDialog::resizeEvent(QResizeEvent *event)
{
resizer_->move(rect().bottomRight() - resizer_->rect().bottomRight());
resizer_->raise();
}
void SyncErrorsDialog::updateErrors()
{
model_->updateErrors();
}
void SyncErrorsDialog::onModelReset()
{
if (model_->rowCount() == 0) {
stack_->setCurrentIndex(INDEX_EMPTY_VIEW);
} else {
stack_->setCurrentIndex(INDEX_TABE_VIEW);
}
}
void SyncErrorsDialog::createEmptyView()
{
empty_view_ = new QWidget(this);
QVBoxLayout *layout = new QVBoxLayout;
empty_view_->setLayout(layout);
QLabel *label = new QLabel;
label->setText(tr("No sync errors."));
label->setAlignment(Qt::AlignCenter);
layout->addWidget(label);
}
SyncErrorsTableView::SyncErrorsTableView(QWidget *parent)
: QTableView(parent)
{
verticalHeader()->hide();
verticalHeader()->setDefaultSectionSize(36);
horizontalHeader()->setSectionResizeMode(QHeaderView::ResizeToContents);
horizontalHeader()->setStretchLastSection(true);
horizontalHeader()->setCascadingSectionResizes(true);
horizontalHeader()->setHighlightSections(false);
horizontalHeader()->setDefaultAlignment(Qt::AlignLeft | Qt::AlignVCenter);
setGridStyle(Qt::NoPen);
setShowGrid(false);
setContentsMargins(0, 0, 0, 0);
setSizePolicy(QSizePolicy::Maximum, QSizePolicy::Maximum);
setSelectionBehavior(QAbstractItemView::SelectRows);
setSelectionMode(QAbstractItemView::ExtendedSelection);
setMouseTracking(true);
createContextMenu();
connect(this, SIGNAL(doubleClicked(const QModelIndex&)),
this, SLOT(onItemDoubleClicked(const QModelIndex&)));
}
void SyncErrorsTableView::contextMenuEvent(QContextMenuEvent *event)
{
QPoint pos = event->pos();
int row = rowAt(pos.y());
qDebug("row = %d\n", row);
if (row == -1) {
return;
}
SyncErrorsTableModel *model = (SyncErrorsTableModel *)this->model();
SyncError error = model->errorAt(row);
prepareContextMenu(error);
pos = viewport()->mapToGlobal(pos);
context_menu_->exec(pos);
}
void SyncErrorsTableView::prepareContextMenu(const SyncError& error)
{
}
void SyncErrorsTableView::createContextMenu()
{
context_menu_ = new QMenu(this);
}
void SyncErrorsTableView::resizeEvent(QResizeEvent *event)
{
QTableView::resizeEvent(event);
SyncErrorsTableModel *m = (SyncErrorsTableModel *)(model());
m->onResize(event->size());
}
void SyncErrorsTableView::onItemDoubleClicked(const QModelIndex& index)
{
SyncErrorsTableModel *model = (SyncErrorsTableModel *)this->model();
SyncError error = model->errorAt(index.row());
// printf("error repo id is %s\n", error.repo_id.toUtf8().data());
if (!error.repo_id.isEmpty()) {
LocalRepo repo;
seafApplet->rpcClient()->getLocalRepo(error.repo_id, &repo);
if (repo.isValid()) {
QDesktopServices::openUrl(QUrl::fromLocalFile(repo.worktree));
}
}
}
SyncErrorsTableModel::SyncErrorsTableModel(QObject *parent)
: QAbstractTableModel(parent),
repo_name_column_width_(kRepoNameColumnWidth),
path_column_width_(kPathColumnWidth),
error_column_width_(kErrorColumnWidth)
{
update_timer_ = new QTimer(this);
connect(update_timer_, SIGNAL(timeout()), this, SLOT(updateErrors()));
update_timer_->start(kUpdateErrorsIntervalMSecs);
updateErrors();
}
void SyncErrorsTableModel::updateErrors()
{
std::vector<SyncError> errors;
int ret = seafApplet->rpcClient()->getSyncErrors(&errors, 0, 50);
if (ret < 0) {
qDebug("failed to get sync errors");
return;
}
// SyncError fake_error;
// fake_error.repo_id = "xxx";
// fake_error.repo_name = "NotSoGood";
// fake_error.path = "/tmp/NotSoGood/BadFile";
// fake_error.error_id = 5;
// fake_error.timestamp = 1483056000;
// fake_error.translateErrorStr();
// errors.push_back(fake_error);
if (errors_ == errors) {
return;
}
if (errors_.size() != errors.size()) {
beginResetModel();
errors_ = errors;
endResetModel();
return;
}
for (int i = 0, n = errors.size(); i < n; i++) {
if (errors_[i] == errors[i]) {
continue;
}
errors_[i] = errors[i];
QModelIndex start = QModelIndex().child(i, 0);
QModelIndex stop = QModelIndex().child(i, MAX_COLUMN - 1);
emit dataChanged(start, stop);
}
}
int SyncErrorsTableModel::rowCount(const QModelIndex& parent) const
{
return errors_.size();
}
int SyncErrorsTableModel::columnCount(const QModelIndex& parent) const
{
return MAX_COLUMN;
}
void SyncErrorsTableModel::onResize(const QSize &size)
{
int extra_width = size.width() - kDefaultColumnSum;
int extra_width_per_column = extra_width / 3;
repo_name_column_width_ = kRepoNameColumnWidth + extra_width_per_column;
path_column_width_ = kPathColumnWidth + extra_width_per_column;
error_column_width_ = kErrorColumnWidth + extra_width_per_column;
// name_column_width_ should be always larger than kPathColumnWidth
if (errors_.empty())
return;
// printf ("path_column_width_ = %d\n", path_column_width_);
emit dataChanged(
index(0, COLUMN_ERROR_STR),
index(errors_.size() - 1 , COLUMN_ERROR_STR));
}
QVariant SyncErrorsTableModel::data(const QModelIndex & index, int role) const
{
if (!index.isValid()) {
return QVariant();
}
int column = index.column();
if (role == Qt::TextAlignmentRole)
return Qt::AlignLeft + Qt::AlignVCenter;
if (role == Qt::ToolTipRole)
return tr("Double click to open the library");
if (role == Qt::SizeHintRole) {
int h = kDefaultColumnHeight;
int w = kDefaultColumnWidth;
switch (column) {
case COLUMN_REPO_NAME:
w = repo_name_column_width_;
break;
case COLUMN_PATH:
w = path_column_width_;
break;
case COLUMN_ERROR_STR:
w = error_column_width_;
break;
case COLUMN_TIMESTAMP:
w = kTimestampColumnWidth;
break;
default:
break;
}
return QSize(w, h);
}
if (role != Qt::DisplayRole) {
return QVariant();
}
const SyncError &error = errors_[index.row()];
if (column == COLUMN_REPO_NAME) {
return error.repo_name;
} else if (column == COLUMN_PATH) {
return QDir::toNativeSeparators(error.path);
} else if (column == COLUMN_ERROR_STR) {
return error.error_str;
} else if (column == COLUMN_TIMESTAMP) {
return error.readable_time_stamp;
}
return QVariant();
}
QVariant SyncErrorsTableModel::headerData(int section,
Qt::Orientation orientation,
int role) const
{
if (orientation == Qt::Vertical) {
return QVariant();
}
if (role == Qt::TextAlignmentRole)
return Qt::AlignLeft + Qt::AlignVCenter;
if (role != Qt::DisplayRole)
return QVariant();
if (section == COLUMN_REPO_NAME) {
return tr("Library");
} else if (section == COLUMN_PATH) {
return tr("Path");
} else if (section == COLUMN_ERROR_STR) {
return tr("Error");
} else if (section == COLUMN_TIMESTAMP) {
return tr("Time");
}
return QVariant();
}

109
src/ui/sync-errors-dialog.h Normal file
View file

@ -0,0 +1,109 @@
#ifndef SEAFILE_CLIENT_SYNC_ERRORS_DIALOG_H
#define SEAFILE_CLIENT_SYNC_ERRORS_DIALOG_H
#include <vector>
#include <QTableView>
#include <QHeaderView>
#include <QAbstractTableModel>
#include <QDialog>
#include "rpc/sync-error.h"
class QTimer;
class QStackedWidget;
class QResizeEvent;
class QSizeGrip;
class QLabel;
class QEvent;
class SyncError;
class SyncErrorsTableView;
class SyncErrorsTableModel;
class SyncErrorsDialog : public QDialog
{
Q_OBJECT
public:
SyncErrorsDialog(QWidget *parent=0);
void updateErrors();
void resizeEvent(QResizeEvent *event);
void closeEvent(QCloseEvent *event);
private slots:
void onModelReset();
private:
void createTitleBar();
void createEmptyView();
bool eventFilter(QObject *obj, QEvent *event);
// title bar (windows and osx only)
QWidget *header_;
QLabel *brand_label_;
QPushButton *minimize_button_;
QPushButton *close_button_;
QPoint old_pos_;
QSizeGrip *resizer_;
QStackedWidget *stack_;
SyncErrorsTableView *table_;
SyncErrorsTableModel *model_;
QWidget *empty_view_;
};
class SyncErrorsTableView : public QTableView
{
Q_OBJECT
public:
SyncErrorsTableView(QWidget *parent=0);
void contextMenuEvent(QContextMenuEvent *event);
void resizeEvent(QResizeEvent *event);
private slots:
void onItemDoubleClicked(const QModelIndex& index);
private:
void createContextMenu();
void prepareContextMenu(const SyncError& error);
private:
QMenu *context_menu_;
};
class SyncErrorsTableModel : public QAbstractTableModel
{
Q_OBJECT
public:
SyncErrorsTableModel(QObject *parent=0);
int rowCount(const QModelIndex& parent=QModelIndex()) const;
int columnCount(const QModelIndex& parent=QModelIndex()) const;
QVariant data(const QModelIndex & index, int role = Qt::DisplayRole) const;
QVariant headerData(int section, Qt::Orientation orientation, int role) const;
SyncError errorAt(size_t i) const { return (i >= errors_.size()) ? SyncError() : errors_[i]; }
void onResize(const QSize& size);
public slots:
void updateErrors();
private:
std::vector<SyncError> errors_;
QTimer *update_timer_;
int repo_name_column_width_;
int path_column_width_;
int error_column_width_;
};
#endif // SEAFILE_CLIENT_SYNC_ERRORS_DIALOG_H

View file

@ -31,6 +31,7 @@ extern "C" {
#include "seahub-notifications-monitor.h"
#include "server-status-service.h"
#include "api/commit-details.h"
#include "sync-errors-dialog.h"
#include "tray-icon.h"
#if defined(Q_OS_MAC)
@ -112,7 +113,8 @@ SeafileTrayIcon::SeafileTrayIcon(QObject *parent)
nth_trayicon_(0),
rotate_counter_(0),
state_(STATE_DAEMON_UP),
next_message_msec_(0)
next_message_msec_(0),
sync_errors_dialog_(nullptr)
{
setState(STATE_DAEMON_DOWN);
rotate_timer_ = new QTimer(this);
@ -185,6 +187,10 @@ void SeafileTrayIcon::createActions()
open_log_directory_action_->setStatusTip(tr("open %1 log folder").arg(getBrand()));
connect(open_log_directory_action_, SIGNAL(triggered()), this, SLOT(openLogDirectory()));
show_sync_errors_action_ = new QAction(tr("Show file sync errors"), this);
show_sync_errors_action_->setStatusTip(tr("Show file sync errors"));
connect(show_sync_errors_action_, SIGNAL(triggered()), this, SLOT(showSyncErrorsDialog()));
about_action_ = new QAction(tr("&About"), this);
about_action_->setStatusTip(tr("Show the application's About box"));
connect(about_action_, SIGNAL(triggered()), this, SLOT(about()));
@ -206,6 +212,7 @@ void SeafileTrayIcon::createContextMenu()
context_menu_->addAction(open_seafile_folder_action_);
context_menu_->addAction(settings_action_);
context_menu_->addAction(open_log_directory_action_);
context_menu_->addAction(show_sync_errors_action_);
// context_menu_->addMenu(help_menu_);
context_menu_->addSeparator();
context_menu_->addAction(about_action_);
@ -245,6 +252,7 @@ void SeafileTrayIcon::createGlobalMenuBar()
global_menu_->addAction(open_seafile_folder_action_);
global_menu_->addAction(settings_action_);
global_menu_->addAction(open_log_directory_action_);
global_menu_->addAction(show_sync_errors_action_);
global_menu_->addSeparator();
global_menu_->addAction(enable_auto_sync_action_);
global_menu_->addAction(disable_auto_sync_action_);
@ -653,3 +661,17 @@ void SeafileTrayIcon::checkTrayIconMessageQueue()
commit_id_ = msg.commit_id;
next_message_msec_ = now + kMessageDisplayTimeMSecs;
}
void SeafileTrayIcon::showSyncErrorsDialog()
{
// CloneTasksDialog dialog(this);
if (sync_errors_dialog_ == nullptr) {
sync_errors_dialog_ = new SyncErrorsDialog;
}
sync_errors_dialog_->updateErrors();
sync_errors_dialog_->show();
sync_errors_dialog_->raise();
sync_errors_dialog_->activateWindow();
}

View file

@ -12,6 +12,8 @@ class QMenuBar;
class TrayNotificationManager;
#endif
class SyncErrorsDialog;
class SeafileTrayIcon : public QSystemTrayIcon {
Q_OBJECT
@ -68,6 +70,8 @@ private slots:
// only used on windows
void onMessageClicked();
void showSyncErrorsDialog();
private:
Q_DISABLE_COPY(SeafileTrayIcon)
@ -92,6 +96,7 @@ private:
QAction *settings_action_;
QAction *open_seafile_folder_action_;
QAction *open_log_directory_action_;
QAction *show_sync_errors_action_;
QAction *view_unread_seahub_notifications_action_;
QAction *about_action_;
@ -128,6 +133,8 @@ private:
// displayed at least several seconds.
QQueue<TrayMessage> pending_messages_;
qint64 next_message_msec_;
SyncErrorsDialog *sync_errors_dialog_;
};
#endif // SEAFILE_CLIENT_TRAY_ICON_H

View file

@ -117,7 +117,7 @@ int count_process (const char *process_name_in)
HANDLE hProcess;
DWORD length;
int count = 0;
int i, j;
DWORD i;
if (strstr(process_name_in, ".exe")) {
snprintf (name, sizeof(name), "%s", process_name_in);