diff --git a/CMakeLists.txt b/CMakeLists.txt index dc5d998..a06c713 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -55,6 +55,7 @@ set(WIDGETS_UI_FILES gui/traywidget.ui gui/connectionoptionpage.ui gui/notificationsoptionpage.ui + gui/appearanceoptionpage.ui gui/launcheroptionpage.ui gui/webviewoptionpage.ui ) @@ -77,6 +78,24 @@ set(DOC_FILES README.md ) +set(REQUIRED_ICONS + network-card + window-close + edit-copy + preferences-other + view-barcode + folder-open + media-playback-start + text-plain + help-about + media-playback-pause + view-refresh + folder + network-server + folder-sync + internet-web-browser +) + # find c++utilities find_package(c++utilities 4.0.0 REQUIRED) use_cpp_utilities() diff --git a/README.md b/README.md index ed4763a..3946655 100644 --- a/README.md +++ b/README.md @@ -31,7 +31,7 @@ I will provide packages for Arch Linux and Windows when releasing. For more info ## Build instructions The application depends on [c++utilities](https://github.com/Martchus/cpp-utilities) and [qtutilities](https://github.com/Martchus/qtutilities) and is built the same way as these libaries. For basic instructions checkout the README file of [c++utilities](https://github.com/Martchus/cpp-utilities). -The following Qt 5 modules are requried: core network gui network widgets webenginewidgets/webkitwidgets +The following Qt 5 modules are requried: core network gui widgets webenginewidgets/webkitwidgets #### Select Qt modules for WebView * If Qt WebKitWidgets is installed on the system, the tray will link against it. Otherwise it will link against Qt WebEngineWidgets. diff --git a/application/settings.cpp b/application/settings.cpp index 7a61b4b..7861582 100644 --- a/application/settings.cpp +++ b/application/settings.cpp @@ -56,6 +56,11 @@ bool &showSyncthingNotifications() static bool v = true; return v; } +bool &showTraffic() +{ + static bool v = true; + return v; +} bool &launchSynchting() { static bool v = false; @@ -111,7 +116,8 @@ void restore() notifyOnDisconnect() = settings.value(QStringLiteral("notifyOnDisconnect"), true).toBool(); notifyOnErrors() = settings.value(QStringLiteral("notifyOnErrors"), true).toBool(); notifyOnSyncComplete() = settings.value(QStringLiteral("notifyOnSyncComplete"), true).toBool(); - notifyOnSyncComplete() = settings.value(QStringLiteral("showSyncthingNotifications"), true).toBool(); + showSyncthingNotifications() = settings.value(QStringLiteral("showSyncthingNotifications"), true).toBool(); + showTraffic() = settings.value(QStringLiteral("showTraffic"), true).toBool(); launchSynchting() = settings.value(QStringLiteral("launchSynchting"), false).toBool(); syncthingCommand() = settings.value(QStringLiteral("syncthingCommand"), QStringLiteral("syncthing")).toString(); settings.endGroup(); @@ -142,6 +148,7 @@ void save() settings.setValue(QStringLiteral("notifyOnErrors"), notifyOnErrors()); settings.setValue(QStringLiteral("notifyOnSyncComplete"), notifyOnSyncComplete()); settings.setValue(QStringLiteral("showSyncthingNotifications"), showSyncthingNotifications()); + settings.setValue(QStringLiteral("showTraffic"), showTraffic()); settings.setValue(QStringLiteral("launchSynchting"), launchSynchting()); settings.setValue(QStringLiteral("syncthingCommand"), syncthingCommand()); settings.endGroup(); diff --git a/application/settings.h b/application/settings.h index 68c44cb..5c35417 100644 --- a/application/settings.h +++ b/application/settings.h @@ -29,6 +29,7 @@ bool ¬ifyOnDisconnect(); bool ¬ifyOnErrors(); bool ¬ifyOnSyncComplete(); bool &showSyncthingNotifications(); +bool &showTraffic(); bool &launchSynchting(); QString &syncthingCommand(); diff --git a/data/syncthingconnection.cpp b/data/syncthingconnection.cpp index 1ba4be1..bf47cd4 100644 --- a/data/syncthingconnection.cpp +++ b/data/syncthingconnection.cpp @@ -26,6 +26,37 @@ QNetworkAccessManager &networkAccessManager() return networkAccessManager; } +/*! + * \brief Assigns the status from the specified status string. + * \returns Returns whether the status has actually changed. + */ +bool SyncthingDir::assignStatus(const QString &statusStr) +{ + DirStatus newStatus; + if(statusStr == QLatin1String("idle")) { + progressPercentage = 0; + newStatus = DirStatus::Idle; + } else if(statusStr == QLatin1String("scanning")) { + newStatus = DirStatus::Scanning; + } else if(statusStr == QLatin1String("syncing")) { + if(!errors.empty()) { + errors.clear(); // errors become obsolete + status = DirStatus::Unknown; // ensure status changed signal is emitted + } + newStatus = DirStatus::Synchronizing; + } else if(statusStr == QLatin1String("error")) { + progressPercentage = 0; + newStatus = DirStatus::OutOfSync; + } else { + newStatus = DirStatus::Unknown; + } + if(newStatus != status) { + status = newStatus; + return true; + } + return false; +} + /*! * \class SyncthingConnection * \brief The SyncthingConnection class allows Qt applications to access Syncthing. @@ -418,8 +449,6 @@ void SyncthingConnection::readDirs(const QJsonArray &dirs) dirItem.ignorePermissions = dirObj.value(QStringLiteral("ignorePerms")).toBool(false); dirItem.autoNormalize = dirObj.value(QStringLiteral("autoNormalize")).toBool(false); dirItem.minDiskFreePercentage = dirObj.value(QStringLiteral("minDiskFreePct")).toInt(-1); - dirItem.status = DirStatus::Unknown; - dirItem.progressPercentage = 0; m_dirs.emplace_back(move(dirItem)); } } @@ -446,9 +475,6 @@ void SyncthingConnection::readDevs(const QJsonArray &devs) devItem.certName = devObj.value(QStringLiteral("certName")).toString(); devItem.introducer = devObj.value(QStringLiteral("introducer")).toBool(false); devItem.status = devItem.id == m_myId ? DevStatus::OwnDevice : DevStatus::Unknown; - devItem.progressPercentage = 0; - devItem.paused = false; - devItem.totalIncomingTraffic = devItem.totalOutgoingTraffic = 0; m_devs.push_back(move(devItem)); } } @@ -518,6 +544,7 @@ void SyncthingConnection::readConnections() const QJsonObject totalObj(replyObj.value(QStringLiteral("total")).toObject()); m_totalIncomingTraffic = totalObj.value(QStringLiteral("inBytesTotal")).toInt(0); m_totalOutgoingTraffic = totalObj.value(QStringLiteral("outBytesTotal")).toInt(0); + emit trafficChanged(m_totalIncomingTraffic, m_totalOutgoingTraffic); const QJsonObject connectionsObj(replyObj.value(QStringLiteral("connections")).toObject()); int index = 0; for(SyncthingDev &dev : m_devs) { @@ -587,6 +614,10 @@ void SyncthingConnection::readEvents() readStartingEvent(eventData); } else if(eventType == QLatin1String("StateChanged")) { readStatusChangedEvent(eventData); + } else if(eventType == QLatin1String("DownloadProgress")) { + readDownloadProgressEvent(eventData); + } else if(eventType.startsWith(QLatin1String("Folder"))) { + readDirEvent(eventType, eventData); } else if(eventType.startsWith(QLatin1String("Device"))) { readDeviceEvent(eventType, eventData); } @@ -651,24 +682,65 @@ void SyncthingConnection::readStatusChangedEvent(const QJsonObject &eventData) const QString dir(eventData.value(QStringLiteral("folder")).toString()); if(!dir.isEmpty()) { // dir status changed - int row; - if(SyncthingDir *dirInfo = findDirInfo(dir, row)) { - const QString statusStr(eventData.value(QStringLiteral("to")).toString()); - DirStatus status; - if(statusStr == QLatin1String("idle")) { - status = DirStatus::Idle; - } else if(statusStr == QLatin1String("scanning")) { - status = DirStatus::Scanning; - } else if(statusStr == QLatin1String("syncing")) { - status = DirStatus::Synchronizing; - } else if(statusStr == QLatin1String("error")) { - status = DirStatus::OutOfSync; - } else { - status = DirStatus::Unknown; + int index; + if(SyncthingDir *dirInfo = findDirInfo(dir, index)) { + if(dirInfo->assignStatus(eventData.value(QStringLiteral("to")).toString())) { + emit dirStatusChanged(*dirInfo, index); } - if(dirInfo->status != status) { - dirInfo->status = status; - emit dirStatusChanged(*dirInfo, row); + } + } +} + +/*! + * \brief Reads results of requestEvents(). + * \remarks TODO + */ +void SyncthingConnection::readDownloadProgressEvent(const QJsonObject &eventData) +{} + +void SyncthingConnection::readDirEvent(const QString &eventType, const QJsonObject &eventData) +{ + const QString dir(eventData.value(QStringLiteral("folder")).toString()); + if(!dir.isEmpty()) { + int index; + if(SyncthingDir *dirInfo = findDirInfo(dir, index)) { + if(eventType == QLatin1String("FolderErrors")) { + // check for errors + const QJsonArray errors(eventData.value(QStringLiteral("errors")).toArray()); + if(!errors.isEmpty()) { + for(const QJsonValue &errorVal : errors) { + const QJsonObject error(errorVal.toObject()); + if(!error.isEmpty()) { + dirInfo->errors.emplace_back(error.value(QStringLiteral("error")).toString(), error.value(QStringLiteral("path")).toString()); + emit newNotification(dirInfo->errors.back().message); + } + } + emit dirStatusChanged(*dirInfo, index); + } + } else if(eventType == QLatin1String("FolderSummary")) { + // check for summary + const QJsonObject summary(eventData.value(QStringLiteral("summary")).toObject()); + if(!summary.isEmpty()) { + dirInfo->globalBytes = summary.value(QStringLiteral("globalBytes")).toInt(); + dirInfo->globalDeleted = summary.value(QStringLiteral("globalDeleted")).toInt(); + dirInfo->globalFiles = summary.value(QStringLiteral("globalFiles")).toInt(); + dirInfo->localBytes = summary.value(QStringLiteral("localBytes")).toInt(); + dirInfo->localDeleted = summary.value(QStringLiteral("localDeleted")).toInt(); + dirInfo->localFiles = summary.value(QStringLiteral("localFiles")).toInt(); + dirInfo->neededByted = summary.value(QStringLiteral("needByted")).toInt(); + dirInfo->neededFiles = summary.value(QStringLiteral("needFiles")).toInt(); + //dirInfo->assignStatus(summary.value(QStringLiteral("state")).toString()); + emit dirStatusChanged(*dirInfo, index); + } + } else if(eventType == QLatin1String("FolderCompletion")) { + // check for progress percentage + //const QString device(eventData.value(QStringLiteral("device")).toString()); + int percentage = eventData.value(QStringLiteral("completion")).toInt(); + if(percentage > 0 && percentage < 100 && (dirInfo->progressPercentage <= 0 || percentage < dirInfo->progressPercentage)) { + // Syncthing provides progress percentage for each device + // just show the smallest percentage for now + dirInfo->progressPercentage = percentage; + } } } } @@ -681,9 +753,9 @@ void SyncthingConnection::readDeviceEvent(const QString &eventType, const QJsonO { const QString dev(eventData.value(QStringLiteral("device")).toString()); if(!dev.isEmpty()) { - // dev status changed - int row; - if(SyncthingDev *devInfo = findDevInfo(dev, row)) { + // dev status changed, depending on event type + int index; + if(SyncthingDev *devInfo = findDevInfo(dev, index)) { DevStatus status = devInfo->status; bool paused = devInfo->paused; if(eventType == QLatin1String("DeviceConnected")) { @@ -707,7 +779,7 @@ void SyncthingConnection::readDeviceEvent(const QString &eventType, const QJsonO devInfo->status = status; } devInfo->paused = paused; - emit devStatusChanged(*devInfo, row); + emit devStatusChanged(*devInfo, index); } } } diff --git a/data/syncthingconnection.h b/data/syncthingconnection.h index 6006cfa..c584ec1 100644 --- a/data/syncthingconnection.h +++ b/data/syncthingconnection.h @@ -36,19 +36,35 @@ enum class DirStatus OutOfSync }; -struct SyncthingDir +struct DirErrors { + DirErrors(const QString &message, const QString &path) : + message(message), + path(path) + {} + QString message; + QString path; +}; + +struct SyncthingDir +{ QString id; QString label; QString path; QStringList devices; - bool readOnly; - bool ignorePermissions; - bool autoNormalize; - int rescanInterval; - int minDiskFreePercentage; - DirStatus status; - int progressPercentage; + bool readOnly = false; + bool ignorePermissions = false; + bool autoNormalize = false; + int rescanInterval = 0; + int minDiskFreePercentage = 0; + DirStatus status = DirStatus::Unknown; + int progressPercentage = 0; + std::vector errors; + int globalBytes = 0, globalDeleted = 0, globalFiles = 0; + int localBytes = 0, localDeleted = 0, localFiles = 0; + int neededByted = 0, neededFiles = 0; + + bool assignStatus(const QString &statusStr); }; enum class DevStatus @@ -70,11 +86,11 @@ struct SyncthingDev QString compression; QString certName; DevStatus status; - int progressPercentage; - bool introducer; - bool paused; - int totalIncomingTraffic; - int totalOutgoingTraffic; + int progressPercentage = 0; + bool introducer = false; + bool paused = false; + int totalIncomingTraffic = 0; + int totalOutgoingTraffic = 0; QString connectionAddress; QString connectionType; QString clientVersion; @@ -94,6 +110,8 @@ class SyncthingConnection : public QObject Q_PROPERTY(SyncthingStatus status READ status NOTIFY statusChanged) Q_PROPERTY(QString configDir READ configDir NOTIFY configDirChanged) Q_PROPERTY(QString myId READ myId NOTIFY myIdChanged) + Q_PROPERTY(int totalIncomingTraffic READ totalIncomingTraffic NOTIFY trafficChanged) + Q_PROPERTY(int totalOutgoingTraffic READ totalOutgoingTraffic NOTIFY trafficChanged) public: explicit SyncthingConnection(const QString &syncthingUrl = QStringLiteral("http://localhost:8080"), const QByteArray &apiKey = QByteArray(), QObject *parent = nullptr); @@ -111,6 +129,8 @@ public: bool isConnected() const; const QString &configDir() const; const QString &myId() const; + int totalIncomingTraffic() const; + int totalOutgoingTraffic() const; const std::vector &dirInfo() const; const std::vector &devInfo() const; void requestQrCode(const QString &text, std::function callback); @@ -191,6 +211,11 @@ Q_SIGNALS: */ void myIdChanged(const QString &myNewId); + /*! + * \brief Indicates totalIncomingTraffic() or totalOutgoingTraffic() has changed. + */ + void trafficChanged(int totalIncomingTraffic, int totalOutgoingTraffic); + private Q_SLOTS: void requestConfig(); void requestStatus(); @@ -206,6 +231,8 @@ private Q_SLOTS: void readEvents(); void readStartingEvent(const QJsonObject &eventData); void readStatusChangedEvent(const QJsonObject &eventData); + void readDownloadProgressEvent(const QJsonObject &eventData); + void readDirEvent(const QString &eventType, const QJsonObject &eventData); void readDeviceEvent(const QString &eventType, const QJsonObject &eventData); void readRescan(); void readPauseResume(); @@ -331,6 +358,22 @@ inline const QString &SyncthingConnection::myId() const return m_myId; } +/*! + * \brief Returns the total incoming traffic. + */ +inline int SyncthingConnection::totalIncomingTraffic() const +{ + return m_totalIncomingTraffic; +} + +/*! + * \brief Returns the total outgoing traffic. + */ +inline int SyncthingConnection::totalOutgoingTraffic() const +{ + return m_totalOutgoingTraffic; +} + /*! * \brief Returns all available directory info. * \remarks The returned object container object is persistent. However, the contained diff --git a/gui/appearanceoptionpage.ui b/gui/appearanceoptionpage.ui new file mode 100644 index 0000000..253e360 --- /dev/null +++ b/gui/appearanceoptionpage.ui @@ -0,0 +1,33 @@ + + + QtGui::AppearanceOptionPage + + + Appearance + + + + + + Show traffic + + + + + + + Qt::Vertical + + + + 20 + 40 + + + + + + + + + diff --git a/gui/devbuttonsitemdelegate.cpp b/gui/devbuttonsitemdelegate.cpp index b44e188..cb882f5 100644 --- a/gui/devbuttonsitemdelegate.cpp +++ b/gui/devbuttonsitemdelegate.cpp @@ -23,8 +23,8 @@ inline int centerObj(int avail, int size) DevButtonsItemDelegate::DevButtonsItemDelegate(QObject* parent) : QStyledItemDelegate(parent), - m_pauseIcon(QIcon::fromTheme(QStringLiteral("media-playback-pause")).pixmap(QSize(16, 16))), - m_resumeIcon(QIcon::fromTheme(QStringLiteral("media-playback-start")).pixmap(QSize(16, 16))) + m_pauseIcon(QIcon::fromTheme(QStringLiteral("media-playback-pause"), QIcon(QStringLiteral(":/icons/hicolor/scalable/actions/media-playback-pause.svg"))).pixmap(QSize(16, 16))), + m_resumeIcon(QIcon::fromTheme(QStringLiteral("media-playback-start"), QIcon(QStringLiteral(":/icons/hicolor/scalable/actions/media-playback-start.svg"))).pixmap(QSize(16, 16))) {} void DevButtonsItemDelegate::paint(QPainter *painter, const QStyleOptionViewItem &option, const QModelIndex &index) const diff --git a/gui/devview.cpp b/gui/devview.cpp index 6f0bc65..5f28ccc 100644 --- a/gui/devview.cpp +++ b/gui/devview.cpp @@ -41,7 +41,7 @@ void DevView::showContextMenu() { if(selectionModel() && selectionModel()->selectedRows(0).size() == 1) { QMenu menu; - connect(menu.addAction(QIcon::fromTheme(QStringLiteral("edit-copy")), tr("Copy")), &QAction::triggered, this, &DevView::copySelectedItem); + connect(menu.addAction(QIcon::fromTheme(QStringLiteral("edit-copy"), QIcon(QStringLiteral(":/icons/hicolor/scalable/actions/edit-copy.svg"))), tr("Copy")), &QAction::triggered, this, &DevView::copySelectedItem); menu.exec(QCursor::pos()); } } diff --git a/gui/dirbuttonsitemdelegate.cpp b/gui/dirbuttonsitemdelegate.cpp index 3b22e31..b7d8b9a 100644 --- a/gui/dirbuttonsitemdelegate.cpp +++ b/gui/dirbuttonsitemdelegate.cpp @@ -18,8 +18,8 @@ inline int centerObj(int avail, int size) DirButtonsItemDelegate::DirButtonsItemDelegate(QObject* parent) : QStyledItemDelegate(parent), - m_refreshIcon(QIcon::fromTheme(QStringLiteral("view-refresh")).pixmap(QSize(16, 16))), - m_folderIcon(QIcon::fromTheme(QStringLiteral("folder-open")).pixmap(QSize(16, 16))) + m_refreshIcon(QIcon::fromTheme(QStringLiteral("view-refresh"), QIcon(QStringLiteral(":/icons/hicolor/scalable/actions/view-refresh.svg"))).pixmap(QSize(16, 16))), + m_folderIcon(QIcon::fromTheme(QStringLiteral("folder-open"), QIcon(QStringLiteral(":/icons/hicolor/scalable/places/folder-open.svg"))).pixmap(QSize(16, 16))) {} void DirButtonsItemDelegate::paint(QPainter *painter, const QStyleOptionViewItem &option, const QModelIndex &index) const diff --git a/gui/dirview.cpp b/gui/dirview.cpp index 661eef8..eaa5a15 100644 --- a/gui/dirview.cpp +++ b/gui/dirview.cpp @@ -41,7 +41,7 @@ void DirView::showContextMenu() { if(selectionModel() && selectionModel()->selectedRows(0).size() == 1) { QMenu menu; - connect(menu.addAction(QIcon::fromTheme(QStringLiteral("edit-copy")), tr("Copy")), &QAction::triggered, this, &DirView::copySelectedItem); + connect(menu.addAction(QIcon::fromTheme(QStringLiteral("edit-copy"), QIcon(QStringLiteral(":/icons/hicolor/scalable/actions/edit-copy.svg"))), tr("Copy")), &QAction::triggered, this, &DirView::copySelectedItem); menu.exec(QCursor::pos()); } } diff --git a/gui/launcheroptionpage.ui b/gui/launcheroptionpage.ui index 208d5d5..d6c6741 100644 --- a/gui/launcheroptionpage.ui +++ b/gui/launcheroptionpage.ui @@ -13,7 +13,15 @@ Launcher - + + + + + <html><head/><body><p><span style=" font-weight:600;">Not implemented yet.</span></p><p>This will allow launching Syncthing when the tray is started.</p></body></html> + + + + diff --git a/gui/notificationsoptionpage.ui b/gui/notificationsoptionpage.ui index ff2f2a3..658c950 100644 --- a/gui/notificationsoptionpage.ui +++ b/gui/notificationsoptionpage.ui @@ -2,6 +2,14 @@ QtGui::NotificationsOptionPage + + + 0 + 0 + 175 + 156 + + Notifications @@ -16,7 +24,14 @@ - Notify on errors + Notify on internal errors + + + + + + + Notify on Syncthing errors @@ -27,13 +42,6 @@ - - - - Show Syncthing notifications - - - diff --git a/gui/settingsdialog.cpp b/gui/settingsdialog.cpp index 8064437..3333da9 100644 --- a/gui/settingsdialog.cpp +++ b/gui/settingsdialog.cpp @@ -5,6 +5,7 @@ #include "ui_connectionoptionpage.h" #include "ui_notificationsoptionpage.h" +#include "ui_appearanceoptionpage.h" #include "ui_launcheroptionpage.h" #include "ui_webviewoptionpage.h" @@ -113,6 +114,29 @@ void NotificationsOptionPage::reset() } } +// AppearanceOptionPage +AppearanceOptionPage::AppearanceOptionPage(QWidget *parentWidget) : + AppearanceOptionPageBase(parentWidget) +{} + +AppearanceOptionPage::~AppearanceOptionPage() +{} + +bool AppearanceOptionPage::apply() +{ + if(hasBeenShown()) { + showTraffic() = ui()->showTrafficCheckBox->isChecked(); + } + return true; +} + +void AppearanceOptionPage::reset() +{ + if(hasBeenShown()) { + ui()->showTrafficCheckBox->setChecked(showTraffic()); + } +} + // LauncherOptionPage LauncherOptionPage::LauncherOptionPage(QWidget *parentWidget) : LauncherOptionPageBase(parentWidget) @@ -187,14 +211,14 @@ SettingsDialog::SettingsDialog(Data::SyncthingConnection *connection, QWidget *p category->setDisplayName(tr("Tray")); category->assignPages(QList() << new ConnectionOptionPage(connection) << new NotificationsOptionPage - << new LauncherOptionPage); + << new AppearanceOptionPage << new LauncherOptionPage); category->setIcon(QIcon(QStringLiteral(":/icons/hicolor/scalable/app/syncthingtray.svg"))); categories << category; category = new OptionCategory(this); category->setDisplayName(tr("Web view")); category->assignPages(QList() << new WebViewOptionPage); - category->setIcon(QIcon::fromTheme(QStringLiteral("internet-web-browser"), QIcon(QStringLiteral(":/icons/hicolor/scalable/app/")))); + category->setIcon(QIcon::fromTheme(QStringLiteral("internet-web-browser"), QIcon(QStringLiteral(":/icons/hicolor/scalable/apps/internet-web-browser.svg")))); categories << category; categories << Settings::qtSettings().category(); @@ -202,7 +226,7 @@ SettingsDialog::SettingsDialog(Data::SyncthingConnection *connection, QWidget *p categoryModel()->setCategories(categories); setMinimumSize(800, 450); - setWindowIcon(QIcon::fromTheme(QStringLiteral("preferences-other"))); + setWindowIcon(QIcon::fromTheme(QStringLiteral("preferences-other"), QIcon(QStringLiteral(":/icons/hicolor/scalable/apps/preferences-other.svg")))); // some settings could be applied without restarting the application, good idea? //connect(this, &Dialogs::SettingsDialog::applied, bind(&Dialogs::QtSettings::apply, &Settings::qtSettings())); diff --git a/gui/settingsdialog.h b/gui/settingsdialog.h index dab83c4..e4c2dad 100644 --- a/gui/settingsdialog.h +++ b/gui/settingsdialog.h @@ -30,6 +30,8 @@ END_DECLARE_OPTION_PAGE DECLARE_UI_FILE_BASED_OPTION_PAGE(NotificationsOptionPage) +DECLARE_UI_FILE_BASED_OPTION_PAGE(AppearanceOptionPage) + DECLARE_UI_FILE_BASED_OPTION_PAGE(LauncherOptionPage) #if defined(SYNCTHINGTRAY_USE_WEBENGINE) || defined(SYNCTHINGTRAY_USE_WEBKIT) diff --git a/gui/tray.cpp b/gui/tray.cpp index ad31ba7..87a3ec8 100644 --- a/gui/tray.cpp +++ b/gui/tray.cpp @@ -17,6 +17,8 @@ #include #include +#include + #include #include #include @@ -25,8 +27,6 @@ #include #include #include -#include -#include #include #include #include @@ -37,6 +37,7 @@ #include using namespace ApplicationUtilities; +using namespace ConversionUtilities; using namespace Dialogs; using namespace Data; using namespace std; @@ -76,30 +77,33 @@ TrayWidget::TrayWidget(TrayMenu *parent) : cornerFrame->setLayout(cornerFrameLayout); auto *viewIdButton = new QPushButton(cornerFrame); viewIdButton->setToolTip(tr("View own device ID")); - viewIdButton->setIcon(QIcon::fromTheme(QStringLiteral("view-barcode"))); + viewIdButton->setIcon(QIcon::fromTheme(QStringLiteral("view-barcode"), QIcon(QStringLiteral(":/icons/hicolor/scalable/actions/view-barcode.svg")))); viewIdButton->setFlat(true); cornerFrameLayout->addWidget(viewIdButton); auto *showLogButton = new QPushButton(cornerFrame); showLogButton->setToolTip(tr("Show Syncthing log")); - showLogButton->setIcon(QIcon::fromTheme(QStringLiteral("text-plain"))); + showLogButton->setIcon(QIcon::fromTheme(QStringLiteral("text-x-generic"), QIcon(QStringLiteral(":/icons/hicolor/scalable/actions/text-x-generic.svg")))); showLogButton->setFlat(true); connect(showLogButton, &QPushButton::clicked, this, &TrayWidget::showLog); cornerFrameLayout->addWidget(showLogButton); auto *scanAllButton = new QPushButton(cornerFrame); scanAllButton->setToolTip(tr("Rescan all directories")); - scanAllButton->setIcon(QIcon::fromTheme(QStringLiteral("folder-sync"))); + scanAllButton->setIcon(QIcon::fromTheme(QStringLiteral("folder-sync"), QIcon(QStringLiteral(":/icons/hicolor/scalable/actions/folder-sync.svg")))); scanAllButton->setFlat(true); cornerFrameLayout->addWidget(scanAllButton); m_ui->tabWidget->setCornerWidget(cornerFrame, Qt::BottomRightCorner); + m_ui->trafficIconLabel->setPixmap(QIcon::fromTheme(QStringLiteral("network-card"), QIcon(QStringLiteral(":/icons/hicolor/scalable/devices/network-card.svg"))).pixmap(32)); + // connect signals and slots - connect(m_ui->statusPushButton, &QPushButton::clicked, this, &TrayWidget::handleStatusButtonClicked); + connect(m_ui->statusPushButton, &QPushButton::clicked, this, &TrayWidget::changeStatus); connect(m_ui->closePushButton, &QPushButton::clicked, &QApplication::quit); connect(m_ui->aboutPushButton, &QPushButton::clicked, this, &TrayWidget::showAboutDialog); connect(m_ui->webUiPushButton, &QPushButton::clicked, this, &TrayWidget::showWebUi); connect(m_ui->settingsPushButton, &QPushButton::clicked, this, &TrayWidget::showSettingsDialog); connect(&m_connection, &SyncthingConnection::statusChanged, this, &TrayWidget::updateStatusButton); + connect(&m_connection, &SyncthingConnection::trafficChanged, this, &TrayWidget::updateTraffic); connect(m_ui->dirsTreeView, &DirView::openDir, this, &TrayWidget::openDir); connect(m_ui->dirsTreeView, &DirView::scanDir, this, &TrayWidget::scanDir); connect(m_ui->devsTreeView, &DevView::pauseResumeDev, this, &TrayWidget::pauseResumeDev); @@ -133,7 +137,7 @@ void TrayWidget::showSettingsDialog() void TrayWidget::showAboutDialog() { if(!m_aboutDlg) { - m_aboutDlg = new AboutDialog(this, tr("Tray application for Syncthing"), QImage(QStringLiteral(":/icons/hicolor/scalable/app/syncthingtray.svg"))); + m_aboutDlg = new AboutDialog(this, QString(), QStringLiteral(APP_AUTHOR "\nfallback icons from KDE/Breeze project\nSyncthing icons from Syncthing project"), QString(), QString(), tr("Tray application for Syncthing"), QImage(QStringLiteral(":/icons/hicolor/scalable/app/syncthingtray.svg"))); m_aboutDlg->setWindowTitle(tr("About - Syncthing Tray")); m_aboutDlg->setWindowIcon(QIcon(QStringLiteral(":/icons/hicolor/scalable/app/syncthingtray.svg"))); } @@ -234,19 +238,19 @@ void TrayWidget::updateStatusButton(SyncthingStatus status) case SyncthingStatus::Disconnected: m_ui->statusPushButton->setText(tr("Connect")); m_ui->statusPushButton->setToolTip(tr("Not connected to Syncthing, click to connect")); - m_ui->statusPushButton->setIcon(QIcon::fromTheme(QStringLiteral("view-refresh"))); + m_ui->statusPushButton->setIcon(QIcon::fromTheme(QStringLiteral("view-refresh"), QIcon(QStringLiteral(":/icons/hicolor/scalable/actions/view-refresh.svg")))); break; case SyncthingStatus::Default: case SyncthingStatus::NotificationsAvailable: case SyncthingStatus::Synchronizing: m_ui->statusPushButton->setText(tr("Pause")); m_ui->statusPushButton->setToolTip(tr("Syncthing is running, click to pause all devices")); - m_ui->statusPushButton->setIcon(QIcon::fromTheme(QStringLiteral("media-playback-pause"))); + m_ui->statusPushButton->setIcon(QIcon::fromTheme(QStringLiteral("media-playback-pause"), QIcon(QStringLiteral(":/icons/hicolor/scalable/actions/media-playback-pause.svg")))); break; case SyncthingStatus::Paused: m_ui->statusPushButton->setText(tr("Continue")); m_ui->statusPushButton->setToolTip(tr("At least one device is paused, click to resume")); - m_ui->statusPushButton->setIcon(QIcon::fromTheme(QStringLiteral("media-playback-start"))); + m_ui->statusPushButton->setIcon(QIcon::fromTheme(QStringLiteral("media-playback-start"), QIcon(QStringLiteral(":/icons/hicolor/scalable/actions/media-playback-resume.svg")))); break; } } @@ -261,6 +265,7 @@ void TrayWidget::applySettings() m_connection.setCredentials(QString(), QString()); } m_connection.reconnect(); + m_ui->trafficFrame->setVisible(Settings::showTraffic()); } void TrayWidget::openDir(const QModelIndex &dirIndex) @@ -292,7 +297,7 @@ void TrayWidget::pauseResumeDev(const QModelIndex &devIndex) } } -void TrayWidget::handleStatusButtonClicked() +void TrayWidget::changeStatus() { switch(m_connection.status()) { case SyncthingStatus::Disconnected: @@ -309,6 +314,12 @@ void TrayWidget::handleStatusButtonClicked() } } +void TrayWidget::updateTraffic(int totalIncomingTraffic, int totalOutgoingTraffic) +{ + m_ui->inTrafficLabel->setText(totalIncomingTraffic >= 0 ? QString::fromUtf8(dataSizeToString(totalIncomingTraffic).data()) : tr("unknown")); + m_ui->outTrafficLabel->setText(totalOutgoingTraffic >= 0 ? QString::fromUtf8(dataSizeToString(totalOutgoingTraffic).data()) : tr("unknown")); +} + void TrayWidget::handleWebViewDeleted() { m_webViewDlg = nullptr; @@ -343,11 +354,11 @@ TrayIcon::TrayIcon(QObject *parent) : m_status(SyncthingStatus::Disconnected) { // set context menu - connect(m_contextMenu.addAction(QIcon::fromTheme(QStringLiteral("internet-web-browser")), tr("Web UI")), &QAction::triggered, m_trayMenu.widget(), &TrayWidget::showWebUi); - connect(m_contextMenu.addAction(QIcon::fromTheme(QStringLiteral("preferences-other")), tr("Settings")), &QAction::triggered, m_trayMenu.widget(), &TrayWidget::showSettingsDialog); - connect(m_contextMenu.addAction(QIcon::fromTheme(QStringLiteral("help-about")), tr("About")), &QAction::triggered, m_trayMenu.widget(), &TrayWidget::showAboutDialog); + connect(m_contextMenu.addAction(QIcon::fromTheme(QStringLiteral("internet-web-browser"), QIcon(QStringLiteral(":/icons/hicolor/scalable/apps/internet-web-browser.svg"))), tr("Web UI")), &QAction::triggered, m_trayMenu.widget(), &TrayWidget::showWebUi); + connect(m_contextMenu.addAction(QIcon::fromTheme(QStringLiteral("preferences-other"), QIcon(QStringLiteral(":/icons/hicolor/scalable/apps/preferences-other.svg"))), tr("Settings")), &QAction::triggered, m_trayMenu.widget(), &TrayWidget::showSettingsDialog); + connect(m_contextMenu.addAction(QIcon::fromTheme(QStringLiteral("help-about"), QIcon(QStringLiteral(":/icons/hicolor/scalable/apps/help-about.svg"))), tr("About")), &QAction::triggered, m_trayMenu.widget(), &TrayWidget::showAboutDialog); m_contextMenu.addSeparator(); - connect(m_contextMenu.addAction(QIcon::fromTheme(QStringLiteral("window-close")), tr("Close")), &QAction::triggered, &QCoreApplication::quit); + connect(m_contextMenu.addAction(QIcon::fromTheme(QStringLiteral("window-close"), QIcon(QStringLiteral(":/icons/hicolor/scalable/actions/window-close.svg"))), tr("Close")), &QAction::triggered, &QCoreApplication::quit); setContextMenu(&m_contextMenu); // set initial status @@ -372,22 +383,11 @@ void TrayIcon::handleActivated(QSystemTrayIcon::ActivationReason reason) if(false) { m_trayMenu.widget()->showWebUi(); } else { + m_trayMenu.resize(m_trayMenu.sizeHint()); // when showing the menu manually // move the menu to the closest of the currently available screen // this implies that the tray icon is located near the edge of the screen; otherwise this behavior makes no sense - const QPoint cursorPos(QCursor::pos()); - const QRect availableGeometry(QApplication::desktop()->availableGeometry(cursorPos)); - Qt::Alignment alignment = 0; - alignment |= (cursorPos.x() - availableGeometry.left() < availableGeometry.right() - cursorPos.x() ? Qt::AlignLeft : Qt::AlignRight); - alignment |= (cursorPos.y() - availableGeometry.top() < availableGeometry.bottom() - cursorPos.y() ? Qt::AlignTop : Qt::AlignBottom); - m_trayMenu.setGeometry( - QStyle::alignedRect( - Qt::LeftToRight, - alignment, - m_trayMenu.sizeHint(), - availableGeometry - ) - ); + cornerWidget(&m_trayMenu); m_trayMenu.show(); } break; diff --git a/gui/tray.h b/gui/tray.h index d079921..f50f5f4 100644 --- a/gui/tray.h +++ b/gui/tray.h @@ -54,7 +54,8 @@ private slots: void openDir(const QModelIndex &dirIndex); void scanDir(const QModelIndex &dirIndex); void pauseResumeDev(const QModelIndex &devIndex); - void handleStatusButtonClicked(); + void changeStatus(); + void updateTraffic(int totalIncomingTraffic, int totalOutgoingTraffic); void handleWebViewDeleted(); private: diff --git a/gui/traywidget.ui b/gui/traywidget.ui index bbbf1f7..9f82536 100644 --- a/gui/traywidget.ui +++ b/gui/traywidget.ui @@ -155,6 +155,98 @@ + + + + QFrame::StyledPanel + + + QFrame::Sunken + + + + 0 + + + 3 + + + 0 + + + 3 + + + 0 + + + + + + 32 + 32 + + + + Traffic + + + + + + + + 2 + + + 2 + + + 2 + + + 2 + + + 2 + + + + + In + + + + + + + unknown + + + + + + + Out + + + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + + + + + + unknown + + + + + + + + + diff --git a/resources/icons.qrc b/resources/icons.qrc index e0ed680..64038c1 100644 --- a/resources/icons.qrc +++ b/resources/icons.qrc @@ -8,5 +8,22 @@ icons/hicolor/scalable/status/syncthing-disconnected.svg icons/hicolor/scalable/status/syncthing-ok.svg icons/hicolor/scalable/status/syncthing-error.svg + icons/hicolor/scalable/actions/application-menu.svg + icons/hicolor/scalable/actions/edit-copy.svg + icons/hicolor/scalable/actions/folder-sync.svg + icons/hicolor/scalable/actions/help-about.svg + icons/hicolor/scalable/actions/media-playback-pause.svg + icons/hicolor/scalable/actions/media-playback-start.svg + icons/hicolor/scalable/actions/view-barcode.svg + icons/hicolor/scalable/actions/view-refresh.svg + icons/hicolor/scalable/actions/window-close.svg + icons/hicolor/scalable/apps/help-about.svg + icons/hicolor/scalable/apps/internet-web-browser.svg + icons/hicolor/scalable/apps/preferences-other.svg + icons/hicolor/scalable/devices/network-card.svg + icons/hicolor/scalable/mimetypes/text-x-generic.svg + icons/hicolor/scalable/places/folder-open.svg + icons/hicolor/scalable/places/folder.svg + icons/hicolor/scalable/places/network-workgroup.svg diff --git a/resources/icons/hicolor/scalable/actions/application-menu.svg b/resources/icons/hicolor/scalable/actions/application-menu.svg new file mode 100644 index 0000000..67dd4d9 --- /dev/null +++ b/resources/icons/hicolor/scalable/actions/application-menu.svg @@ -0,0 +1,13 @@ + + + + + + diff --git a/resources/icons/hicolor/scalable/actions/edit-copy.svg b/resources/icons/hicolor/scalable/actions/edit-copy.svg new file mode 100644 index 0000000..5ae93b8 --- /dev/null +++ b/resources/icons/hicolor/scalable/actions/edit-copy.svg @@ -0,0 +1,13 @@ + + + + + + diff --git a/resources/icons/hicolor/scalable/actions/folder-sync.svg b/resources/icons/hicolor/scalable/actions/folder-sync.svg new file mode 100644 index 0000000..5b75047 --- /dev/null +++ b/resources/icons/hicolor/scalable/actions/folder-sync.svg @@ -0,0 +1,13 @@ + + + + + + diff --git a/resources/icons/hicolor/scalable/actions/help-about.svg b/resources/icons/hicolor/scalable/actions/help-about.svg new file mode 100644 index 0000000..d4fa772 --- /dev/null +++ b/resources/icons/hicolor/scalable/actions/help-about.svg @@ -0,0 +1,13 @@ + + + + + + diff --git a/resources/icons/hicolor/scalable/actions/media-playback-pause.svg b/resources/icons/hicolor/scalable/actions/media-playback-pause.svg new file mode 100644 index 0000000..4c49c4e --- /dev/null +++ b/resources/icons/hicolor/scalable/actions/media-playback-pause.svg @@ -0,0 +1,13 @@ + + + + + + diff --git a/resources/icons/hicolor/scalable/actions/media-playback-start.svg b/resources/icons/hicolor/scalable/actions/media-playback-start.svg new file mode 100644 index 0000000..48045f9 --- /dev/null +++ b/resources/icons/hicolor/scalable/actions/media-playback-start.svg @@ -0,0 +1,13 @@ + + + + + + diff --git a/resources/icons/hicolor/scalable/actions/view-barcode.svg b/resources/icons/hicolor/scalable/actions/view-barcode.svg new file mode 100644 index 0000000..ae2f10b --- /dev/null +++ b/resources/icons/hicolor/scalable/actions/view-barcode.svg @@ -0,0 +1,13 @@ + + + + + + diff --git a/resources/icons/hicolor/scalable/actions/view-refresh.svg b/resources/icons/hicolor/scalable/actions/view-refresh.svg new file mode 100644 index 0000000..b0ab47a --- /dev/null +++ b/resources/icons/hicolor/scalable/actions/view-refresh.svg @@ -0,0 +1,13 @@ + + + + + + diff --git a/resources/icons/hicolor/scalable/actions/window-close.svg b/resources/icons/hicolor/scalable/actions/window-close.svg new file mode 100644 index 0000000..1fc40d9 --- /dev/null +++ b/resources/icons/hicolor/scalable/actions/window-close.svg @@ -0,0 +1,17 @@ + + + + + + diff --git a/resources/icons/hicolor/scalable/apps/help-about.svg b/resources/icons/hicolor/scalable/apps/help-about.svg new file mode 100644 index 0000000..1b3d1c7 --- /dev/null +++ b/resources/icons/hicolor/scalable/apps/help-about.svg @@ -0,0 +1,28 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/resources/icons/hicolor/scalable/apps/internet-web-browser.svg b/resources/icons/hicolor/scalable/apps/internet-web-browser.svg new file mode 100644 index 0000000..3108022 --- /dev/null +++ b/resources/icons/hicolor/scalable/apps/internet-web-browser.svg @@ -0,0 +1,17 @@ + + + + + + + + + + + + + + + + + diff --git a/resources/icons/hicolor/scalable/apps/preferences-other.svg b/resources/icons/hicolor/scalable/apps/preferences-other.svg new file mode 100644 index 0000000..8e41272 --- /dev/null +++ b/resources/icons/hicolor/scalable/apps/preferences-other.svg @@ -0,0 +1,28 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/resources/icons/hicolor/scalable/devices/network-card.svg b/resources/icons/hicolor/scalable/devices/network-card.svg new file mode 100644 index 0000000..29b2ec2 --- /dev/null +++ b/resources/icons/hicolor/scalable/devices/network-card.svg @@ -0,0 +1,55 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/resources/icons/hicolor/scalable/mimetypes/text-x-generic.svg b/resources/icons/hicolor/scalable/mimetypes/text-x-generic.svg new file mode 100644 index 0000000..f14fac9 --- /dev/null +++ b/resources/icons/hicolor/scalable/mimetypes/text-x-generic.svg @@ -0,0 +1,8 @@ + + + + + + + + diff --git a/resources/icons/hicolor/scalable/places/folder-open.svg b/resources/icons/hicolor/scalable/places/folder-open.svg new file mode 100644 index 0000000..a962e99 --- /dev/null +++ b/resources/icons/hicolor/scalable/places/folder-open.svg @@ -0,0 +1,14 @@ + + + + + + diff --git a/resources/icons/hicolor/scalable/places/folder.svg b/resources/icons/hicolor/scalable/places/folder.svg new file mode 100644 index 0000000..871f94a --- /dev/null +++ b/resources/icons/hicolor/scalable/places/folder.svg @@ -0,0 +1,13 @@ + + + + + + diff --git a/resources/icons/hicolor/scalable/places/network-workgroup.svg b/resources/icons/hicolor/scalable/places/network-workgroup.svg new file mode 100644 index 0000000..7c7b1cd --- /dev/null +++ b/resources/icons/hicolor/scalable/places/network-workgroup.svg @@ -0,0 +1,13 @@ + + + + + + diff --git a/translations/syncthingtray_de_DE.ts b/translations/syncthingtray_de_DE.ts index 8609124..1843efd 100644 --- a/translations/syncthingtray_de_DE.ts +++ b/translations/syncthingtray_de_DE.ts @@ -4,89 +4,89 @@ Data::SyncthingConnection - + disconnected - + connected - + connected, notifications available - + connected, paused - + connected, synchronizing - + unknown - + Unable to parse Syncthing log: - + Unable to request system log: - - + + Unable to parse Syncthing config: - - + + Unable to request Syncthing config: - + Unable to parse connections: - + Unable to request connections: - + Unable to parse Syncthing events: - + Unable to request Syncthing events: - + Unable to request rescan: - + Unable to request pause/resume: - + Unable to request QR-Code: @@ -269,6 +269,19 @@ + + QtGui::AppearanceOptionPage + + + Appearance + + + + + Show traffic + + + QtGui::ConnectionOptionPage @@ -340,44 +353,49 @@ Launcher + + + <html><head/><body><p><span style=" font-weight:600;">Not implemented yet.</span></p><p>This will allow launching Syncthing when the tray is started.</p></body></html> + + QtGui::NotificationsOptionPage - + Notifications - + Notify on disconnect - - Notify on errors + + Notify on internal errors - + + Notify on Syncthing errors + + + + Notify on sync complete - - - Show Syncthing notifications - - QtGui::SettingsDialog - + Tray - + Web view @@ -385,22 +403,22 @@ QtGui::TrayIcon - + Web UI - + Settings - + About - + Close @@ -464,17 +482,40 @@ - + Connect - + + Traffic + + + + + In + + + + + + + + unknown + + + + + Out + + + + Directories - + Devices @@ -494,82 +535,82 @@ - + View own device ID - + Settings - Syncthing tray - + Tray application for Syncthing - + Rescan all directories - + Show Syncthing log - + About - Syncthing Tray - + Own device ID - Syncthing Tray - + device ID is unknown - + Copy to clipboard - + Log - Syncthing - + 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 - + Continue - + The directly <i>%1</i> does not exist on the local machine. @@ -586,7 +627,7 @@ QtGui::WebViewOptionPage - + General @@ -616,7 +657,7 @@ - + Syncthing Tray has not been built with vieb view support utilizing either Qt WebKit or Qt WebEngine. The Web UI will be opened in the default web browser instead. diff --git a/translations/syncthingtray_en_US.ts b/translations/syncthingtray_en_US.ts index 104ede9..959aa61 100644 --- a/translations/syncthingtray_en_US.ts +++ b/translations/syncthingtray_en_US.ts @@ -4,89 +4,89 @@ Data::SyncthingConnection - + disconnected - + connected - + connected, notifications available - + connected, paused - + connected, synchronizing - + unknown - + Unable to parse Syncthing log: - + Unable to request system log: - - + + Unable to parse Syncthing config: - - + + Unable to request Syncthing config: - + Unable to parse connections: - + Unable to request connections: - + Unable to parse Syncthing events: - + Unable to request Syncthing events: - + Unable to request rescan: - + Unable to request pause/resume: - + Unable to request QR-Code: @@ -269,6 +269,19 @@ + + QtGui::AppearanceOptionPage + + + Appearance + + + + + Show traffic + + + QtGui::ConnectionOptionPage @@ -340,44 +353,49 @@ Launcher + + + <html><head/><body><p><span style=" font-weight:600;">Not implemented yet.</span></p><p>This will allow launching Syncthing when the tray is started.</p></body></html> + + QtGui::NotificationsOptionPage - + Notifications - + Notify on disconnect - - Notify on errors + + Notify on internal errors - + + Notify on Syncthing errors + + + + Notify on sync complete - - - Show Syncthing notifications - - QtGui::SettingsDialog - + Tray - + Web view @@ -385,22 +403,22 @@ QtGui::TrayIcon - + Web UI - + Settings - + About - + Close @@ -464,17 +482,40 @@ - + Connect - + + Traffic + + + + + In + + + + + + + + unknown + + + + + Out + + + + Directories - + Devices @@ -494,82 +535,82 @@ - + View own device ID - + Settings - Syncthing tray - + Tray application for Syncthing - + Rescan all directories - + Show Syncthing log - + About - Syncthing Tray - + Own device ID - Syncthing Tray - + device ID is unknown - + Copy to clipboard - + Log - Syncthing - + 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 - + Continue - + The directly <i>%1</i> does not exist on the local machine. @@ -586,7 +627,7 @@ QtGui::WebViewOptionPage - + General @@ -616,7 +657,7 @@ - + Syncthing Tray has not been built with vieb view support utilizing either Qt WebKit or Qt WebEngine. The Web UI will be opened in the default web browser instead.