diff --git a/CMakeLists.txt b/CMakeLists.txt index b7f7c5a..6ee8b48 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -45,6 +45,7 @@ set(WIDGETS_HEADER_FILES gui/devbuttonsitemdelegate.h gui/dirview.h gui/devview.h + gui/textviewdialog.h ) set(WIDGETS_SRC_FILES application/main.cpp @@ -59,6 +60,7 @@ set(WIDGETS_SRC_FILES gui/devbuttonsitemdelegate.cpp gui/dirview.cpp gui/devview.cpp + gui/textviewdialog.cpp resources/icons.qrc ) set(WIDGETS_UI_FILES diff --git a/data/syncthingconnection.cpp b/data/syncthingconnection.cpp index 3e89ec9..ab40080 100644 --- a/data/syncthingconnection.cpp +++ b/data/syncthingconnection.cpp @@ -122,6 +122,7 @@ SyncthingConnection::SyncthingConnection(const QString &syncthingUrl, const QByt m_configReply(nullptr), m_statusReply(nullptr), m_connectionsReply(nullptr), + m_errorsReply(nullptr), m_eventsReply(nullptr), m_unreadNotifications(false), m_hasConfig(false), @@ -197,7 +198,7 @@ void SyncthingConnection::reconnect() m_hasConfig = m_hasStatus = false; abortAllRequests(); } else { - continueReconnect(); + continueReconnecting(); } } @@ -224,7 +225,7 @@ void SyncthingConnection::reconnect(Settings::ConnectionSettings &connectionSett /*! * \brief Internally called to reconnect; ensures currently cached config is cleared. */ -void SyncthingConnection::continueReconnect() +void SyncthingConnection::continueReconnecting() { emit newConfig(QJsonObject()); // configuration will be invalidated setStatus(SyncthingStatus::Reconnecting); @@ -244,6 +245,7 @@ void SyncthingConnection::continueReconnect() m_devs.clear(); m_lastConnectionsUpdate = DateTime(); m_lastFileTime = DateTime(); + m_lastErrorTime = DateTime(); m_lastFileName.clear(); m_lastFileDeleted = false; if(m_apiKey.isEmpty() || m_syncthingUrl.isEmpty()) { @@ -380,6 +382,7 @@ void SyncthingConnection::continueConnecting() requestConnections(); requestDirStatistics(); requestDeviceStatistics(); + requestErrors(); // since config and status could be read successfully, let's poll for events m_lastEventId = 0; requestEvents(); @@ -400,6 +403,9 @@ void SyncthingConnection::abortAllRequests() if(m_connectionsReply) { m_connectionsReply->abort(); } + if(m_errorsReply) { + m_errorsReply->abort(); + } if(m_eventsReply) { m_eventsReply->abort(); } @@ -435,6 +441,16 @@ void SyncthingConnection::requestConnections() QObject::connect(m_connectionsReply = requestData(QStringLiteral("system/connections"), QUrlQuery()), &QNetworkReply::finished, this, &SyncthingConnection::readConnections); } +/*! + * \brief Requests errors asynchronously. + * + * The signal newNotification() is emitted on success; error() is emitted in the error case. + */ +void SyncthingConnection::requestErrors() +{ + QObject::connect(m_errorsReply = requestData(QStringLiteral("system/error"), QUrlQuery()), &QNetworkReply::finished, this, &SyncthingConnection::readErrors); +} + /*! * \brief Requests directory statistics asynchronously. */ @@ -509,10 +525,7 @@ QMetaObject::Connection SyncthingConnection::requestLog(std::function(sender()); + reply->deleteLater(); + if(reply == m_errorsReply) { + m_errorsReply = nullptr; + } + + // ignore any errors occured before connecting + if(m_lastErrorTime.isNull()) { + m_lastErrorTime = DateTime::now(); + } + + switch(reply->error()) { + case QNetworkReply::NoError: { + QJsonParseError jsonError; + const QJsonDocument replyDoc = QJsonDocument::fromJson(reply->readAll(), &jsonError); + if(jsonError.error == QJsonParseError::NoError) { + for(const QJsonValue &errorVal : replyDoc.object().value(QStringLiteral("errors")).toArray()) { + const QJsonObject errorObj(errorVal.toObject()); + if(!errorObj.isEmpty()) { + try { + const DateTime when = DateTime::fromIsoStringLocal(errorObj.value(QStringLiteral("when")).toString().toLocal8Bit().data()); + if(m_lastErrorTime < when) { + emitNotification(m_lastErrorTime = when, errorObj.value(QStringLiteral("message")).toString()); + } + } catch(const ConversionException &) { + } + } + } + } else { + emit error(tr("Unable to parse errors: ") + jsonError.errorString()); + } + + // since there seems no event for this data, just request every thirty seconds, FIXME: make interval configurable + if(m_keepPolling) { + QTimer::singleShot(300/*00*/, Qt::VeryCoarseTimer, this, SLOT(requestErrors())); + } + break; + } case QNetworkReply::OperationCanceledError: + return; // intended, not an error + default: + emit error(tr("Unable to request errors: ") + reply->errorString()); + } +} + /*! * \brief Reads results of requestEvents(). */ @@ -937,7 +996,7 @@ void SyncthingConnection::readEvents() // intended disconnect, not an error if(m_reconnecting) { // if reconnection flag is set, instantly etstablish a new connection ... - continueReconnect(); + continueReconnecting(); } else { // ... otherwise keep disconnected setStatus(SyncthingStatus::Disconnected); @@ -1017,7 +1076,7 @@ void SyncthingConnection::readDirEvent(DateTime eventTime, const QString &eventT if(!error.isEmpty()) { dirInfo->errors.emplace_back(error.value(QStringLiteral("error")).toString(), error.value(QStringLiteral("path")).toString()); dirInfo->assignStatus(DirStatus::OutOfSync, eventTime); - emit newNotification(dirInfo->errors.back().message); + emitNotification(eventTime, dirInfo->errors.back().message); } } emit dirStatusChanged(*dirInfo, index); @@ -1139,7 +1198,7 @@ void SyncthingConnection::readItemFinished(DateTime eventTime, const QJsonObject } } else if(dirInfo->status == DirStatus::OutOfSync) { // FIXME: find better way to check whether the event is still relevant dirInfo->errors.emplace_back(error, item); - emit newNotification(error); + emitNotification(eventTime, error); } } } @@ -1208,12 +1267,13 @@ void SyncthingConnection::setStatus(SyncthingStatus status) // check whether at least one directory is scanning or synchronizing bool scanning = false; bool synchronizing = false; - for(const SyncthingDir &dir : m_dirs) + for(const SyncthingDir &dir : m_dirs) { if(dir.status == DirStatus::Synchronizing) { synchronizing = true; break; } else if(dir.status == DirStatus::Scanning) { scanning = true; + } } if(synchronizing) { status = SyncthingStatus::Synchronizing; @@ -1241,4 +1301,15 @@ void SyncthingConnection::setStatus(SyncthingStatus status) } } +/*! + * \brief Interanlly called to emit the notification with the specified \a message. + * \remarks Ensures the status is updated and the unread notifications flag is set. + */ +void SyncthingConnection::emitNotification(DateTime when, const QString &message) +{ + m_unreadNotifications = true; + setStatus(status()); + emit newNotification(when, message); +} + } diff --git a/data/syncthingconnection.h b/data/syncthingconnection.h index 8d03bcc..fea25c7 100644 --- a/data/syncthingconnection.h +++ b/data/syncthingconnection.h @@ -117,6 +117,10 @@ struct SyncthingDev struct SyncthingLogEntry { + SyncthingLogEntry(const QString &when, const QString &message) : + when(when), + message(message) + {} QString when; QString message; }; @@ -214,7 +218,7 @@ Q_SIGNALS: /*! * \brief Indicates a new Syncthing notification is available. */ - void newNotification(const QString &message); + void newNotification(ChronoUtilities::DateTime when, const QString &message); /*! * \brief Indicates a request (for configuration, events, ...) failed. @@ -245,6 +249,7 @@ private Q_SLOTS: void requestConfig(); void requestStatus(); void requestConnections(); + void requestErrors(); void requestDirStatistics(); void requestDeviceStatistics(); void requestEvents(); @@ -257,6 +262,7 @@ private Q_SLOTS: void readConnections(); void readDirStatistics(); void readDeviceStatistics(); + void readErrors(); void readEvents(); void readStartingEvent(const QJsonObject &eventData); void readStatusChangedEvent(ChronoUtilities::DateTime eventTime, const QJsonObject &eventData); @@ -269,8 +275,10 @@ private Q_SLOTS: void readPauseResume(); void readRestart(); - void continueReconnect(); + void continueConnecting(); + void continueReconnecting(); void setStatus(SyncthingStatus status); + void emitNotification(ChronoUtilities::DateTime when, const QString &message); private: QNetworkRequest prepareRequest(const QString &path, const QUrlQuery &query, bool rest = true); @@ -278,7 +286,6 @@ private: QNetworkReply *postData(const QString &path, const QUrlQuery &query, const QByteArray &data = QByteArray()); SyncthingDir *findDirInfo(const QString &dir, int &row); SyncthingDev *findDevInfo(const QString &dev, int &row); - void continueConnecting(); QString m_syncthingUrl; QByteArray m_apiKey; @@ -297,6 +304,7 @@ private: QNetworkReply *m_configReply; QNetworkReply *m_statusReply; QNetworkReply *m_connectionsReply; + QNetworkReply *m_errorsReply; QNetworkReply *m_eventsReply; bool m_unreadNotifications; bool m_hasConfig; @@ -305,6 +313,7 @@ private: std::vector m_devs; ChronoUtilities::DateTime m_lastConnectionsUpdate; ChronoUtilities::DateTime m_lastFileTime; + ChronoUtilities::DateTime m_lastErrorTime; QString m_lastFileName; bool m_lastFileDeleted; QList m_expectedSslErrors; diff --git a/gui/textviewdialog.cpp b/gui/textviewdialog.cpp new file mode 100644 index 0000000..c5dffe2 --- /dev/null +++ b/gui/textviewdialog.cpp @@ -0,0 +1,46 @@ +#include "./textviewdialog.h" + +#include "resources/config.h" + +#include + +#include +#include +#include +#include + +using namespace Dialogs; + +namespace QtGui { + +TextViewDialog::TextViewDialog(const QString &title, QWidget *parent) : + QWidget(parent, Qt::Window) +{ + // set window title and icon + if(title.isEmpty()) { + setWindowTitle(QStringLiteral(APP_NAME)); + } else { + setWindowTitle(title + QStringLiteral(" - " APP_NAME)); + } + setWindowIcon(QIcon(QStringLiteral(":/icons/hicolor/scalable/app/syncthingtray.svg"))); + + // by default, delete on close + setAttribute(Qt::WA_DeleteOnClose); + + // setup browser + m_browser = new QTextBrowser(this); + m_browser->setReadOnly(true); + m_browser->setFont(QFontDatabase::systemFont(QFontDatabase::FixedFont)); + + // setup layout + auto *layout = new QVBoxLayout(this); + layout->setAlignment(Qt::AlignCenter); + layout->addWidget(m_browser); + setLayout(layout); + + // default position and size + resize(600, 500); + centerWidget(this); +} + +} diff --git a/gui/textviewdialog.h b/gui/textviewdialog.h new file mode 100644 index 0000000..d38623d --- /dev/null +++ b/gui/textviewdialog.h @@ -0,0 +1,29 @@ +#ifndef TEXTVIEWDIALOG_H +#define TEXTVIEWDIALOG_H + +#include + +QT_FORWARD_DECLARE_CLASS(QTextBrowser) + +namespace QtGui { + +class TextViewDialog : public QWidget +{ + Q_OBJECT +public: + TextViewDialog(const QString &title = QString(), QWidget *parent = nullptr); + + QTextBrowser *browser(); + +private: + QTextBrowser *m_browser; +}; + +inline QTextBrowser *TextViewDialog::browser() +{ + return m_browser; +} + +} + +#endif // TEXTVIEWDIALOG_H diff --git a/gui/trayicon.cpp b/gui/trayicon.cpp index 1b21d03..413a7e8 100644 --- a/gui/trayicon.cpp +++ b/gui/trayicon.cpp @@ -45,6 +45,7 @@ TrayIcon::TrayIcon(QObject *parent) : // connect signals and slots SyncthingConnection *connection = &(m_trayMenu.widget()->connection()); connect(this, &TrayIcon::activated, this, &TrayIcon::handleActivated); + connect(this, &TrayIcon::messageClicked, this, &TrayIcon::handleMessageClicked); connect(connection, &SyncthingConnection::error, this, &TrayIcon::showInternalError); connect(connection, &SyncthingConnection::newNotification, this, &TrayIcon::showSyncthingNotification); connect(connection, &SyncthingConnection::statusChanged, this, &TrayIcon::updateStatusIconAndText); @@ -72,6 +73,11 @@ void TrayIcon::handleActivated(QSystemTrayIcon::ActivationReason reason) } } +void TrayIcon::handleMessageClicked() +{ + m_trayMenu.widget()->connection().notificationsRead(); +} + void TrayIcon::showInternalError(const QString &errorMsg) { if(Settings::notifyOnInternalErrors()) { @@ -79,10 +85,10 @@ void TrayIcon::showInternalError(const QString &errorMsg) } } -void TrayIcon::showSyncthingNotification(const QString &message) +void TrayIcon::showSyncthingNotification(ChronoUtilities::DateTime when, const QString &message) { if(Settings::showSyncthingNotifications()) { - showMessage(tr("Syncthing notification"), message, QSystemTrayIcon::Warning); + showMessage(tr("Syncthing notification - click to dismiss"), message, QSystemTrayIcon::Warning); } } diff --git a/gui/trayicon.h b/gui/trayicon.h index 082d5f6..2bca693 100644 --- a/gui/trayicon.h +++ b/gui/trayicon.h @@ -10,6 +10,10 @@ QT_FORWARD_DECLARE_CLASS(QPixmap) +namespace ChronoUtilities { +class DateTime; +} + namespace Data { enum class SyncthingStatus; } @@ -26,8 +30,9 @@ public: private slots: void handleActivated(QSystemTrayIcon::ActivationReason reason); + void handleMessageClicked(); void showInternalError(const QString &errorMsg); - void showSyncthingNotification(const QString &message); + void showSyncthingNotification(ChronoUtilities::DateTime when, const QString &message); void updateStatusIconAndText(Data::SyncthingStatus status); private: diff --git a/gui/traywidget.cpp b/gui/traywidget.cpp index 5984202..64acd7b 100644 --- a/gui/traywidget.cpp +++ b/gui/traywidget.cpp @@ -2,6 +2,7 @@ #include "./traymenu.h" #include "./settingsdialog.h" #include "./webviewdialog.h" +#include "./textviewdialog.h" #include "../application/settings.h" @@ -30,6 +31,7 @@ using namespace ApplicationUtilities; using namespace ConversionUtilities; +using namespace ChronoUtilities; using namespace Dialogs; using namespace Data; using namespace std; @@ -73,13 +75,11 @@ TrayWidget::TrayWidget(TrayMenu *parent) : restartButton->setToolTip(tr("Restart Syncthing")); restartButton->setIcon(QIcon::fromTheme(QStringLiteral("system-reboot"), QIcon(QStringLiteral(":/icons/hicolor/scalable/actions/view-refresh.svg")))); restartButton->setFlat(true); - connect(restartButton, &QPushButton::clicked, &m_connection, &SyncthingConnection::restart); cornerFrameLayout->addWidget(restartButton); auto *showLogButton = new QPushButton(m_cornerFrame); showLogButton->setToolTip(tr("Show Syncthing log")); showLogButton->setIcon(QIcon::fromTheme(QStringLiteral("text-x-generic"), QIcon(QStringLiteral(":/icons/hicolor/scalable/mimetypes/text-x-generic.svg")))); showLogButton->setFlat(true); - connect(showLogButton, &QPushButton::clicked, this, &TrayWidget::showLog); cornerFrameLayout->addWidget(showLogButton); auto *scanAllButton = new QPushButton(m_cornerFrame); scanAllButton->setToolTip(tr("Rescan all directories")); @@ -96,6 +96,8 @@ TrayWidget::TrayWidget(TrayMenu *parent) : // apply settings, this also establishes the connection to Syncthing applySettings(); + // setup other widgets + m_ui->notificationsPushButton->setHidden(true); m_ui->trafficIconLabel->setPixmap(QIcon::fromTheme(QStringLiteral("network-card"), QIcon(QStringLiteral(":/icons/hicolor/scalable/devices/network-card.svg"))).pixmap(32)); // connect signals and slots @@ -112,6 +114,9 @@ TrayWidget::TrayWidget(TrayMenu *parent) : connect(m_ui->devsTreeView, &DevView::pauseResumeDev, this, &TrayWidget::pauseResumeDev); connect(scanAllButton, &QPushButton::clicked, &m_connection, &SyncthingConnection::rescanAllDirs); connect(viewIdButton, &QPushButton::clicked, this, &TrayWidget::showOwnDeviceId); + connect(showLogButton, &QPushButton::clicked, this, &TrayWidget::showLog); + connect(m_ui->notificationsPushButton, &QPushButton::clicked, this, &TrayWidget::showNotifications); + connect(restartButton, &QPushButton::clicked, &m_connection, &SyncthingConnection::restart); connect(m_connectionsActionGroup, &QActionGroup::triggered, this, &TrayWidget::handleConnectionSelected); } @@ -124,12 +129,8 @@ void TrayWidget::showSettingsDialog() m_settingsDlg = new SettingsDialog(&m_connection, this); connect(m_settingsDlg, &SettingsDialog::applied, this, &TrayWidget::applySettings); } - m_settingsDlg->show(); centerWidget(m_settingsDlg); - if(m_menu) { - m_menu->close(); - } - m_settingsDlg->activateWindow(); + showDialog(m_settingsDlg); } void TrayWidget::showAboutDialog() @@ -139,12 +140,8 @@ void TrayWidget::showAboutDialog() m_aboutDlg->setWindowTitle(tr("About") + QStringLiteral(" - " APP_NAME)); m_aboutDlg->setWindowIcon(QIcon(QStringLiteral(":/icons/hicolor/scalable/app/syncthingtray.svg"))); } - m_aboutDlg->show(); centerWidget(m_aboutDlg); - if(m_menu) { - m_menu->close(); - } - m_aboutDlg->activateWindow(); + showDialog(m_aboutDlg); } void TrayWidget::showWebUi() @@ -162,11 +159,7 @@ void TrayWidget::showWebUi() } connect(m_webViewDlg, &WebViewDialog::destroyed, this, &TrayWidget::handleWebViewDeleted); } - m_webViewDlg->show(); - if(m_menu) { - m_menu->close(); - } - m_webViewDlg->activateWindow(); + showDialog(m_webViewDlg); } #endif } @@ -200,42 +193,34 @@ void TrayWidget::showOwnDeviceId() m_connection.requestQrCode(m_connection.myId(), bind(&QLabel::setPixmap, pixmapLabel, placeholders::_1)) )); dlg->setLayout(layout); - dlg->show(); centerWidget(dlg); - if(m_menu) { - m_menu->close(); - } - dlg->activateWindow(); + showDialog(dlg); } void TrayWidget::showLog() { - auto *dlg = new QWidget(this, Qt::Window); - dlg->setWindowTitle(tr("Log") + QStringLiteral(" - " APP_NAME)); - dlg->setWindowIcon(QIcon(QStringLiteral(":/icons/hicolor/scalable/app/syncthingtray.svg"))); - dlg->setAttribute(Qt::WA_DeleteOnClose); - auto *layout = new QVBoxLayout(dlg); - layout->setAlignment(Qt::AlignCenter); - auto *browser = new QTextBrowser(dlg); + auto *dlg = new TextViewDialog(tr("Log"), this); connect(dlg, &QWidget::destroyed, bind(static_cast(&QObject::disconnect), - m_connection.requestLog([browser] (const std::vector &entries) { + m_connection.requestLog([dlg] (const std::vector &entries) { for(const SyncthingLogEntry &entry : entries) { - browser->append(entry.when % QChar(':') % QChar(' ') % QChar('\n') % entry.message % QChar('\n')); + dlg->browser()->append(entry.when % QChar(':') % QChar(' ') % QChar('\n') % entry.message % QChar('\n')); } }) )); - browser->setReadOnly(true); - browser->setFont(QFontDatabase::systemFont(QFontDatabase::FixedFont)); - layout->addWidget(browser); - dlg->setLayout(layout); - dlg->show(); - dlg->resize(600, 500); - centerWidget(dlg); - if(m_menu) { - m_menu->close(); + showDialog(dlg); +} + +void TrayWidget::showNotifications() +{ + auto *dlg = new TextViewDialog(tr("New notifications"), this); + for(const SyncthingLogEntry &entry : m_notifications) { + dlg->browser()->append(entry.when % QChar(':') % QChar(' ') % QChar('\n') % entry.message % QChar('\n')); } - dlg->activateWindow(); + m_notifications.clear(); + showDialog(dlg); + m_connection.notificationsRead(); + m_ui->notificationsPushButton->setHidden(true); } void TrayWidget::handleStatusChanged(SyncthingStatus status) @@ -312,7 +297,7 @@ void TrayWidget::applySettings() if(Settings::showTraffic()) { updateTraffic(); } - m_ui->trafficFrame->setFrameStyle(Settings::frameStyle()); + m_ui->infoFrame->setFrameStyle(Settings::frameStyle()); m_ui->buttonsFrame->setFrameStyle(Settings::frameStyle()); if(QApplication::style() && !QApplication::style()->objectName().compare(QLatin1String("adwaita"), Qt::CaseInsensitive)) { m_cornerFrame->setFrameStyle(QFrame::NoFrame); @@ -372,7 +357,7 @@ void TrayWidget::changeStatus() void TrayWidget::updateTraffic() { - if(m_ui->trafficFrame->isHidden()) { + if(m_ui->trafficFormWidget->isHidden()) { return; } static const QString unknownStr(tr("unknown")); @@ -405,9 +390,10 @@ void TrayWidget::handleWebViewDeleted() } #endif -void TrayWidget::handleNewNotification(const QString &msg) +void TrayWidget::handleNewNotification(DateTime when, const QString &msg) { - // FIXME + m_notifications.emplace_back(QString::fromLocal8Bit(when.toString(DateTimeOutputFormat::DateAndTime, true).data()), msg); + m_ui->notificationsPushButton->setHidden(false); } void TrayWidget::handleConnectionSelected(QAction *connectionAction) @@ -431,4 +417,13 @@ void TrayWidget::showConnectionsMenu() m_connectionsMenu->exec(QCursor::pos()); } +void TrayWidget::showDialog(QWidget *dlg) +{ + if(m_menu) { + m_menu->close(); + } + dlg->show(); + dlg->activateWindow(); +} + } diff --git a/gui/traywidget.h b/gui/traywidget.h index 4c2d226..03a7710 100644 --- a/gui/traywidget.h +++ b/gui/traywidget.h @@ -52,6 +52,7 @@ public slots: void showWebUi(); void showOwnDeviceId(); void showLog(); + void showNotifications(); private slots: void handleStatusChanged(Data::SyncthingStatus status); @@ -64,9 +65,10 @@ private slots: #ifndef SYNCTHINGTRAY_NO_WEBVIEW void handleWebViewDeleted(); #endif - void handleNewNotification(const QString &msg); + void handleNewNotification(ChronoUtilities::DateTime when, const QString &msg); void handleConnectionSelected(QAction *connectionAction); void showConnectionsMenu(); + void showDialog(QWidget *dlg); private: TrayMenu *m_menu; @@ -83,6 +85,7 @@ private: QMenu *m_connectionsMenu; QActionGroup *m_connectionsActionGroup; Settings::ConnectionSettings *m_selectedConnection; + std::vector m_notifications; }; inline Data::SyncthingConnection &TrayWidget::connection() diff --git a/gui/traywidget.ui b/gui/traywidget.ui index e638949..7149053 100644 --- a/gui/traywidget.ui +++ b/gui/traywidget.ui @@ -160,7 +160,7 @@ - + QFrame::StyledPanel @@ -254,6 +254,35 @@ + + + + + 0 + 0 + + + + + 75 + true + + + + Click to show <i>new</i> notifications<br> +For <i>all</i> notifications, checkout the log + + + New notifications + + + + + + true + + + @@ -263,7 +292,8 @@ - + + .. true diff --git a/translations/syncthingtray_de_DE.ts b/translations/syncthingtray_de_DE.ts index 130ea44..0aea650 100644 --- a/translations/syncthingtray_de_DE.ts +++ b/translations/syncthingtray_de_DE.ts @@ -4,135 +4,145 @@ Data::SyncthingConnection - + disconnected - + reconnecting - + connected - + connected, notifications available - + connected, paused - + connected, synchronizing - + unknown - - + + Connection configuration is insufficient. - + Unable to parse Syncthing log: - + Unable to request system log: - + Unable to locate certificate used by Syncthing GUI. - + Unable to load certificate used by Syncthing GUI. - - + + Unable to parse Syncthing config: - - + + Unable to request Syncthing config: - + Unable to parse connections: - + Unable to request connections: - + Unable to parse directory statistics: - + Unable to request directory statistics: - + Unable to parse device statistics: - + Unable to request device statistics: - + + Unable to parse errors: + + + + + Unable to request errors: + + + + Unable to parse Syncthing events: - + Unable to request Syncthing events: - + Unable to request rescan: - + Unable to request pause/resume: - + Unable to request restart: - + Unable to request QR-Code: @@ -753,57 +763,57 @@ - + Error - - Syncthing notification + + Syncthing notification - click to dismiss - + Not connected to Syncthing - + Disconnected from Syncthing - + Reconnecting ... - + Syncthing is idling - + Syncthing is scanning - + Notifications available - + At least one device is paused - + Synchronization is ongoing - + Synchronization complete @@ -817,7 +827,7 @@ - + Connect @@ -841,10 +851,16 @@ Outgoing traffic + + + Click to show <i>new</i> notifications<br> +For <i>all</i> notifications, checkout the log + + - + unknown @@ -859,18 +875,18 @@ - + Directories - + Devices - + About @@ -885,7 +901,7 @@ - + View own device ID @@ -895,12 +911,12 @@ - + Show Syncthing log - + Restart Syncthing @@ -910,52 +926,58 @@ - + device ID is unknown - + Copy to clipboard - + + + New notifications + + + + Not connected to Syncthing, click to connect - + Pause - + Syncthing is running, click to pause all devices - + At least one device is paused, click to resume - + The directory <i>%1</i> does not exist on the local machine. - + Continue - + Own device ID - + Log diff --git a/translations/syncthingtray_en_US.ts b/translations/syncthingtray_en_US.ts index fecd935..4a90711 100644 --- a/translations/syncthingtray_en_US.ts +++ b/translations/syncthingtray_en_US.ts @@ -4,135 +4,145 @@ Data::SyncthingConnection - + disconnected - + reconnecting - + connected - + connected, notifications available - + connected, paused - + connected, synchronizing - + unknown - - + + Connection configuration is insufficient. - + Unable to parse Syncthing log: - + Unable to request system log: - + Unable to locate certificate used by Syncthing GUI. - + Unable to load certificate used by Syncthing GUI. - - + + Unable to parse Syncthing config: - - + + Unable to request Syncthing config: - + Unable to parse connections: - + Unable to request connections: - + Unable to parse directory statistics: - + Unable to request directory statistics: - + Unable to parse device statistics: - + Unable to request device statistics: - + + Unable to parse errors: + + + + + Unable to request errors: + + + + Unable to parse Syncthing events: - + Unable to request Syncthing events: - + Unable to request rescan: - + Unable to request pause/resume: - + Unable to request restart: - + Unable to request QR-Code: @@ -753,57 +763,57 @@ - + Error - - Syncthing notification + + Syncthing notification - click to dismiss - + Not connected to Syncthing - + Disconnected from Syncthing - + Reconnecting ... - + Syncthing is idling - + Syncthing is scanning - + Notifications available - + At least one device is paused - + Synchronization is ongoing - + Synchronization complete @@ -817,7 +827,7 @@ - + Connect @@ -841,10 +851,16 @@ Outgoing traffic + + + Click to show <i>new</i> notifications<br> +For <i>all</i> notifications, checkout the log + + - + unknown @@ -859,18 +875,18 @@ - + Directories - + Devices - + About @@ -885,7 +901,7 @@ - + View own device ID @@ -895,12 +911,12 @@ - + Show Syncthing log - + Restart Syncthing @@ -910,52 +926,58 @@ - + device ID is unknown - + Copy to clipboard - + + + New notifications + + + + Not connected to Syncthing, click to connect - + Pause - + Syncthing is running, click to pause all devices - + At least one device is paused, click to resume - + The directory <i>%1</i> does not exist on the local machine. - + Continue - + Own device ID - + Log