So far the backend libraries' include paths were relative within this repository. This means the header files could not be used at their installed location. This change replaces them with "<>" includes to fix that problem and adds a new include directory so building everything at once still works. With this change it should be easier to actually split some parts into another repository if this one would become too big.
829 lines
34 KiB
C++
829 lines
34 KiB
C++
#include "./traywidget.h"
|
|
#include "./helper.h"
|
|
#include "./trayicon.h"
|
|
#include "./traymenu.h"
|
|
|
|
#include <syncthingwidgets/misc/otherdialogs.h>
|
|
#include <syncthingwidgets/misc/syncthinglauncher.h>
|
|
#include <syncthingwidgets/misc/textviewdialog.h>
|
|
#include <syncthingwidgets/settings/settingsdialog.h>
|
|
#include <syncthingwidgets/webview/webviewdialog.h>
|
|
|
|
#ifdef SYNCTHINGTRAY_UNIFY_TRAY_MENUS
|
|
#include <syncthingwidgets/misc/internalerrorsdialog.h>
|
|
#endif
|
|
|
|
#include <syncthingmodel/syncthingicons.h>
|
|
|
|
#ifdef LIB_SYNCTHING_CONNECTOR_SUPPORT_SYSTEMD
|
|
#include <syncthingconnector/syncthingservice.h>
|
|
#endif
|
|
#include <syncthingconnector/utils.h>
|
|
|
|
// use meta-data of syncthingtray application here
|
|
#include "resources/../../tray/resources/config.h"
|
|
|
|
#include "ui_traywidget.h"
|
|
|
|
#include <qtutilities/aboutdialog/aboutdialog.h>
|
|
#include <qtutilities/misc/desktoputils.h>
|
|
#include <qtutilities/misc/dialogutils.h>
|
|
#include <qtutilities/resources/qtconfigarguments.h>
|
|
#include <qtutilities/resources/resources.h>
|
|
#include <qtutilities/settingsdialog/qtsettings.h>
|
|
|
|
#include <c++utilities/application/argumentparser.h>
|
|
#include <c++utilities/conversion/stringconversion.h>
|
|
|
|
#include <QActionGroup>
|
|
#include <QClipboard>
|
|
#include <QCoreApplication>
|
|
#include <QDesktopServices>
|
|
#include <QDir>
|
|
#include <QFontDatabase>
|
|
#include <QGuiApplication>
|
|
#include <QMessageBox>
|
|
#include <QPalette>
|
|
#include <QStringBuilder>
|
|
#include <QTextBrowser>
|
|
|
|
#include <algorithm>
|
|
#include <functional>
|
|
|
|
using namespace CppUtilities;
|
|
using namespace QtUtilities;
|
|
using namespace Data;
|
|
using namespace std;
|
|
|
|
namespace QtGui {
|
|
|
|
QWidget *TrayWidget::s_dialogParent = nullptr;
|
|
SettingsDialog *TrayWidget::s_settingsDlg = nullptr;
|
|
QtUtilities::AboutDialog *TrayWidget::s_aboutDlg = nullptr;
|
|
vector<TrayWidget *> TrayWidget::s_instances;
|
|
|
|
/*!
|
|
* \brief Instantiates a new tray widget.
|
|
* \remarks Doesn't apply the settings (and won't connect according to settings). This must be done manually by calling
|
|
* TrayWidget::applySettings(). This allows postponing connecting until all signals of the TrayWidget::connection()
|
|
* are connected as required.
|
|
*/
|
|
TrayWidget::TrayWidget(TrayMenu *parent)
|
|
: QWidget(parent)
|
|
, m_menu(parent)
|
|
, m_ui(new Ui::TrayWidget)
|
|
#ifndef SYNCTHINGWIDGETS_NO_WEBVIEW
|
|
, m_webViewDlg(nullptr)
|
|
#endif
|
|
, m_notifier(m_connection)
|
|
, m_dirModel(m_connection)
|
|
, m_sortFilterDirModel(&m_dirModel)
|
|
, m_devModel(m_connection)
|
|
, m_sortFilterDevModel(&m_devModel)
|
|
, m_dlModel(m_connection)
|
|
, m_recentChangesModel(m_connection)
|
|
, m_selectedConnection(nullptr)
|
|
, m_startStopButtonTarget(StartStopButtonTarget::None)
|
|
{
|
|
// don't show connection status within connection settings if there are multiple tray widgets/icons (would be ambiguous)
|
|
if (!s_instances.empty() && s_settingsDlg) {
|
|
s_settingsDlg->hideConnectionStatus();
|
|
}
|
|
|
|
// take record of the newly created instance
|
|
s_instances.push_back(this);
|
|
|
|
// show the connection configuration in the previous icon's tooltip as soon as there's a 2nd icon
|
|
if (s_instances.size() == 2) {
|
|
s_instances.front()->updateIconAndTooltip();
|
|
}
|
|
|
|
m_ui->setupUi(this);
|
|
|
|
// setup models and views
|
|
m_ui->dirsTreeView->header()->setSortIndicator(0, Qt::AscendingOrder);
|
|
m_ui->dirsTreeView->setModel(&m_sortFilterDirModel);
|
|
m_ui->devsTreeView->header()->setSortIndicator(0, Qt::AscendingOrder);
|
|
m_ui->devsTreeView->setModel(&m_sortFilterDevModel);
|
|
m_ui->downloadsTreeView->setModel(&m_dlModel);
|
|
m_ui->recentChangesTreeView->setModel(&m_recentChangesModel);
|
|
m_ui->recentChangesTreeView->setContextMenuPolicy(Qt::CustomContextMenu);
|
|
|
|
// setup sync-all button
|
|
m_cornerFrame = new QFrame(this);
|
|
auto *cornerFrameLayout = new QHBoxLayout(m_cornerFrame);
|
|
cornerFrameLayout->setSpacing(0);
|
|
cornerFrameLayout->setContentsMargins(0, 0, 0, 0);
|
|
m_cornerFrame->setLayout(cornerFrameLayout);
|
|
auto *viewIdButton = new QPushButton(m_cornerFrame);
|
|
viewIdButton->setToolTip(tr("View own device ID"));
|
|
viewIdButton->setIcon(
|
|
QIcon::fromTheme(QStringLiteral("view-barcode"), QIcon(QStringLiteral(":/icons/hicolor/scalable/actions/view-barcode.svg"))));
|
|
viewIdButton->setFlat(true);
|
|
cornerFrameLayout->addWidget(viewIdButton);
|
|
auto *restartButton = new QPushButton(m_cornerFrame);
|
|
restartButton->setToolTip(tr("Restart Syncthing"));
|
|
restartButton->setIcon(
|
|
QIcon::fromTheme(QStringLiteral("system-reboot"), QIcon(QStringLiteral(":/icons/hicolor/scalable/actions/view-refresh.svg"))));
|
|
restartButton->setFlat(true);
|
|
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);
|
|
cornerFrameLayout->addWidget(showLogButton);
|
|
auto *scanAllButton = new QPushButton(m_cornerFrame);
|
|
scanAllButton->setToolTip(tr("Rescan all directories"));
|
|
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(m_cornerFrame, Qt::BottomRightCorner);
|
|
|
|
// setup connection menu
|
|
m_connectionsActionGroup = new QActionGroup(m_connectionsMenu = new QMenu(tr("Connection"), this));
|
|
m_connectionsMenu->setIcon(
|
|
QIcon::fromTheme(QStringLiteral("network-connect"), QIcon(QStringLiteral(":/icons/hicolor/scalable/actions/network-connect.svg"))));
|
|
m_ui->connectionsPushButton->setMenu(m_connectionsMenu);
|
|
|
|
// setup notifications menu
|
|
m_notificationsMenu = new QMenu(tr("New notifications"), this);
|
|
m_notificationsMenu->addAction(m_ui->actionShowNotifications);
|
|
m_notificationsMenu->addAction(m_ui->actionDismissNotifications);
|
|
m_ui->notificationsPushButton->setMenu(m_notificationsMenu);
|
|
|
|
// setup other widgets
|
|
m_ui->notificationsPushButton->setHidden(true);
|
|
m_ui->globalTextLabel->setPixmap(
|
|
QIcon::fromTheme(QStringLiteral("globe"), QIcon(QStringLiteral(":/icons/hicolor/scalable/actions/globe.svg"))).pixmap(16));
|
|
m_ui->localTextLabel->setPixmap(
|
|
QIcon::fromTheme(QStringLiteral("user-home"), QIcon(QStringLiteral(":/icons/hicolor/scalable/places/user-home.svg"))).pixmap(16));
|
|
updateTraffic();
|
|
|
|
#ifdef SYNCTHINGTRAY_UNIFY_TRAY_MENUS
|
|
// add actions from right-click menu if it is not available
|
|
m_internalErrorsButton = new QPushButton(m_cornerFrame);
|
|
m_internalErrorsButton->setToolTip(tr("Show internal errors"));
|
|
m_internalErrorsButton->setIcon(
|
|
QIcon::fromTheme(QStringLiteral("emblem-error"), QIcon(QStringLiteral(":/icons/hicolor/scalable/emblems/8/emblem-error.svg"))));
|
|
m_internalErrorsButton->setFlat(true);
|
|
m_internalErrorsButton->setVisible(false);
|
|
connect(m_internalErrorsButton, &QPushButton::clicked, this, &TrayWidget::showInternalErrorsDialog);
|
|
cornerFrameLayout->addWidget(m_internalErrorsButton);
|
|
auto *quitButton = new QPushButton(m_cornerFrame);
|
|
quitButton->setToolTip(tr("Quit Syncthing Tray"));
|
|
quitButton->setIcon(QIcon::fromTheme(QStringLiteral("window-close"), QIcon(QStringLiteral(":/icons/hicolor/scalable/actions/window-close.svg"))));
|
|
quitButton->setFlat(true);
|
|
connect(quitButton, &QPushButton::clicked, this, &TrayWidget::quitTray);
|
|
cornerFrameLayout->addWidget(quitButton);
|
|
#endif
|
|
|
|
// connect signals and slots
|
|
connect(m_ui->statusPushButton, &QPushButton::clicked, this, &TrayWidget::changeStatus);
|
|
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::handleStatusChanged);
|
|
connect(&m_connection, &SyncthingConnection::trafficChanged, this, &TrayWidget::updateTraffic);
|
|
connect(&m_connection, &SyncthingConnection::dirStatisticsChanged, this, &TrayWidget::updateOverallStatistics);
|
|
connect(&m_connection, &SyncthingConnection::newNotification, this, &TrayWidget::handleNewNotification);
|
|
connect(m_ui->dirsTreeView, &DirView::openDir, this, &TrayWidget::openDir);
|
|
connect(m_ui->dirsTreeView, &DirView::scanDir, this, &TrayWidget::scanDir);
|
|
connect(m_ui->dirsTreeView, &DirView::pauseResumeDir, this, &TrayWidget::pauseResumeDir);
|
|
connect(m_ui->devsTreeView, &DevView::pauseResumeDev, this, &TrayWidget::pauseResumeDev);
|
|
connect(m_ui->downloadsTreeView, &DownloadView::openDir, this, &TrayWidget::openDir);
|
|
connect(m_ui->downloadsTreeView, &DownloadView::openItemDir, this, &TrayWidget::openItemDir);
|
|
connect(m_ui->recentChangesTreeView, &QTreeView::customContextMenuRequested, this, &TrayWidget::showRecentChangesContextMenu);
|
|
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, this, &TrayWidget::restartSyncthing);
|
|
connect(m_connectionsActionGroup, &QActionGroup::triggered, this, &TrayWidget::handleConnectionSelected);
|
|
connect(m_ui->actionShowNotifications, &QAction::triggered, this, &TrayWidget::showNotifications);
|
|
connect(m_ui->actionDismissNotifications, &QAction::triggered, this, &TrayWidget::dismissNotifications);
|
|
connect(m_ui->startStopPushButton, &QPushButton::clicked, this, &TrayWidget::toggleRunning);
|
|
if (const auto *const launcher = SyncthingLauncher::mainInstance()) {
|
|
connect(launcher, &SyncthingLauncher::runningChanged, this, &TrayWidget::handleLauncherStatusChanged);
|
|
}
|
|
#ifdef LIB_SYNCTHING_CONNECTOR_SUPPORT_SYSTEMD
|
|
if (const auto *const service = SyncthingService::mainInstance()) {
|
|
connect(service, &SyncthingService::systemdAvailableChanged, this, &TrayWidget::handleSystemdStatusChanged);
|
|
connect(service, &SyncthingService::stateChanged, this, &TrayWidget::handleSystemdStatusChanged);
|
|
}
|
|
#endif
|
|
}
|
|
|
|
TrayWidget::~TrayWidget()
|
|
{
|
|
auto i = std::find(s_instances.begin(), s_instances.end(), this);
|
|
if (i != s_instances.end()) {
|
|
s_instances.erase(i);
|
|
}
|
|
if (s_instances.empty()) {
|
|
delete s_dialogParent;
|
|
QCoreApplication::quit();
|
|
} else if (s_instances.size() == 1) {
|
|
s_instances.front()->updateIconAndTooltip();
|
|
}
|
|
}
|
|
|
|
void TrayWidget::showSettingsDialog()
|
|
{
|
|
if (!s_dialogParent) {
|
|
s_dialogParent = new QWidget();
|
|
}
|
|
if (!s_settingsDlg) {
|
|
s_settingsDlg = new SettingsDialog(s_instances.size() < 2 ? &m_connection : nullptr, s_dialogParent);
|
|
connect(s_settingsDlg, &SettingsDialog::applied, &TrayWidget::applySettingsOnAllInstances);
|
|
|
|
// save settings to disk when applied
|
|
// note: QCoreApplication::aboutToQuit() does not work reliably but terminating only at the
|
|
// end of the session is a common use-case for the tray application. So workaround this
|
|
// by simply saving the settings immediately.
|
|
connect(s_settingsDlg, &SettingsDialog::applied, &Settings::save);
|
|
}
|
|
centerWidget(s_settingsDlg);
|
|
showDialog(s_settingsDlg);
|
|
}
|
|
|
|
void TrayWidget::showAboutDialog()
|
|
{
|
|
if (!s_dialogParent) {
|
|
s_dialogParent = new QWidget();
|
|
}
|
|
if (!s_aboutDlg) {
|
|
s_aboutDlg = new AboutDialog(s_dialogParent, QString(),
|
|
QStringLiteral(
|
|
"<p>Developed by " APP_AUTHOR
|
|
"<br>Fallback icons from KDE/Breeze project<br>Syncthing icons from <a href=\"https://syncthing.net\">Syncthing project</a><br>Using "
|
|
"icons from <a href=\"https://fontawesome.com\">Font "
|
|
"Awesome</a> (see <a href=\"https://fontawesome.com/license\">their license</a>)</p>"),
|
|
QString(), {}, QStringLiteral(APP_URL), QString(), renderSvgImage(makeSyncthingIcon(), QSize(128, 128)).toImage());
|
|
s_aboutDlg->setWindowTitle(tr("About") + QStringLiteral(" - " APP_NAME));
|
|
s_aboutDlg->setWindowIcon(QIcon(QStringLiteral(":/icons/hicolor/scalable/app/syncthingtray.svg")));
|
|
}
|
|
centerWidget(s_aboutDlg);
|
|
showDialog(s_aboutDlg);
|
|
}
|
|
|
|
void TrayWidget::showWebUi()
|
|
{
|
|
#ifndef SYNCTHINGWIDGETS_NO_WEBVIEW
|
|
if (Settings::values().webView.disabled) {
|
|
#endif
|
|
QDesktopServices::openUrl(m_connection.syncthingUrl());
|
|
#ifndef SYNCTHINGWIDGETS_NO_WEBVIEW
|
|
} else {
|
|
if (!m_webViewDlg) {
|
|
m_webViewDlg = new WebViewDialog(this);
|
|
if (m_selectedConnection) {
|
|
m_webViewDlg->applySettings(*m_selectedConnection, true);
|
|
}
|
|
connect(m_webViewDlg, &WebViewDialog::destroyed, this, &TrayWidget::handleWebViewDeleted);
|
|
}
|
|
showDialog(m_webViewDlg);
|
|
}
|
|
#endif
|
|
}
|
|
|
|
void TrayWidget::showOwnDeviceId()
|
|
{
|
|
auto *const dlg = ownDeviceIdDialog(m_connection);
|
|
dlg->setAttribute(Qt::WA_DeleteOnClose, true);
|
|
centerWidget(dlg);
|
|
showDialog(dlg);
|
|
}
|
|
|
|
void TrayWidget::showLog()
|
|
{
|
|
auto *const dlg = TextViewDialog::forLogEntries(m_connection);
|
|
dlg->setAttribute(Qt::WA_DeleteOnClose, true);
|
|
centerWidget(dlg);
|
|
showDialog(dlg);
|
|
}
|
|
|
|
void TrayWidget::showNotifications()
|
|
{
|
|
auto *const dlg = TextViewDialog::forLogEntries(m_notifications, tr("New notifications"));
|
|
dlg->setAttribute(Qt::WA_DeleteOnClose, true);
|
|
centerWidget(dlg);
|
|
showDialog(dlg);
|
|
m_notifications.clear();
|
|
dismissNotifications();
|
|
}
|
|
|
|
void TrayWidget::showUsingPositioningSettings()
|
|
{
|
|
if (m_menu) {
|
|
m_menu->showUsingPositioningSettings();
|
|
} else {
|
|
move(Settings::values().appearance.positioning.positionToUse());
|
|
show();
|
|
}
|
|
}
|
|
|
|
#ifdef SYNCTHINGTRAY_UNIFY_TRAY_MENUS
|
|
void TrayWidget::showInternalErrorsButton()
|
|
{
|
|
m_internalErrorsButton->setVisible(true);
|
|
}
|
|
|
|
void TrayWidget::showInternalErrorsDialog()
|
|
{
|
|
auto *const errorViewDlg = InternalErrorsDialog::instance();
|
|
connect(errorViewDlg, &InternalErrorsDialog::errorsCleared, this, &TrayWidget::handleErrorsCleared);
|
|
centerWidget(errorViewDlg);
|
|
errorViewDlg->show();
|
|
}
|
|
#endif
|
|
|
|
void TrayWidget::dismissNotifications()
|
|
{
|
|
m_connection.considerAllNotificationsRead();
|
|
m_ui->notificationsPushButton->setHidden(true);
|
|
if (m_menu && m_menu->icon()) {
|
|
m_menu->icon()->updateStatusIconAndText();
|
|
}
|
|
}
|
|
|
|
void TrayWidget::restartSyncthing()
|
|
{
|
|
if (QMessageBox::warning(
|
|
this, QCoreApplication::applicationName(), tr("Do you really want to restart Syncthing?"), QMessageBox::Yes, QMessageBox::No)
|
|
== QMessageBox::Yes) {
|
|
m_connection.restart();
|
|
}
|
|
}
|
|
|
|
void TrayWidget::quitTray()
|
|
{
|
|
QObject *parent;
|
|
if (m_menu) {
|
|
if (m_menu->icon()) {
|
|
parent = m_menu->icon();
|
|
} else {
|
|
parent = m_menu;
|
|
}
|
|
} else {
|
|
parent = this;
|
|
}
|
|
parent->deleteLater();
|
|
}
|
|
|
|
void TrayWidget::handleStatusChanged(SyncthingStatus status)
|
|
{
|
|
switch (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"), QIcon(QStringLiteral(":/icons/hicolor/scalable/actions/view-refresh.svg"))));
|
|
m_ui->statusPushButton->setHidden(false);
|
|
updateTraffic(); // ensure previous traffic statistics are no longer shown
|
|
break;
|
|
case SyncthingStatus::Reconnecting:
|
|
m_ui->statusPushButton->setHidden(true);
|
|
break;
|
|
case SyncthingStatus::Idle:
|
|
case SyncthingStatus::Scanning:
|
|
case SyncthingStatus::Synchronizing:
|
|
case SyncthingStatus::RemoteNotInSync:
|
|
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"), QIcon(QStringLiteral(":/icons/hicolor/scalable/actions/media-playback-pause.svg"))));
|
|
m_ui->statusPushButton->setHidden(false);
|
|
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"), QIcon(QStringLiteral(":/icons/hicolor/scalable/actions/media-playback-start.svg"))));
|
|
m_ui->statusPushButton->setHidden(false);
|
|
break;
|
|
default:;
|
|
}
|
|
}
|
|
|
|
#ifdef SYNCTHINGTRAY_UNIFY_TRAY_MENUS
|
|
void TrayWidget::handleErrorsCleared()
|
|
{
|
|
m_internalErrorsButton->setVisible(false);
|
|
}
|
|
#endif
|
|
|
|
void TrayWidget::applySettings(const QString &connectionConfig)
|
|
{
|
|
// update connections menu
|
|
int connectionIndex = 0;
|
|
auto &settings = Settings::values();
|
|
auto &primaryConnectionSettings = settings.connection.primary;
|
|
auto &secondaryConnectionSettings = settings.connection.secondary;
|
|
const int connectionCount = static_cast<int>(1 + secondaryConnectionSettings.size());
|
|
const QList<QAction *> connectionActions = m_connectionsActionGroup->actions();
|
|
m_selectedConnection = nullptr;
|
|
bool specifiedConnectionConfigFound = false;
|
|
for (; connectionIndex < connectionCount; ++connectionIndex) {
|
|
SyncthingConnectionSettings &connectionSettings
|
|
= (connectionIndex == 0 ? primaryConnectionSettings : secondaryConnectionSettings[static_cast<size_t>(connectionIndex - 1)]);
|
|
QAction *action;
|
|
if (connectionIndex < connectionActions.size()) {
|
|
action = connectionActions.at(connectionIndex);
|
|
action->setText(connectionSettings.label);
|
|
if (action->isChecked() && !m_selectedConnection) {
|
|
m_selectedConnection = &connectionSettings;
|
|
}
|
|
} else {
|
|
action = m_connectionsMenu->addAction(connectionSettings.label);
|
|
action->setCheckable(true);
|
|
m_connectionsActionGroup->addAction(action);
|
|
}
|
|
if (!connectionConfig.isEmpty() && !connectionSettings.label.compare(connectionConfig, Qt::CaseInsensitive)) {
|
|
m_selectedConnection = &connectionSettings;
|
|
specifiedConnectionConfigFound = true;
|
|
action->setChecked(true);
|
|
}
|
|
}
|
|
for (; connectionIndex < connectionActions.size(); ++connectionIndex) {
|
|
delete connectionActions.at(connectionIndex);
|
|
}
|
|
if (!m_selectedConnection) {
|
|
m_selectedConnection = &primaryConnectionSettings;
|
|
m_connectionsMenu->actions().at(0)->setChecked(true);
|
|
}
|
|
m_ui->connectionsPushButton->setText(m_selectedConnection->label);
|
|
m_ui->connectionsPushButton->setHidden(secondaryConnectionSettings.empty());
|
|
const bool reconnectRequired = m_connection.applySettings(*m_selectedConnection);
|
|
|
|
// apply notification settings
|
|
settings.apply(m_notifier);
|
|
|
|
// apply systemd and launcher settings enforcing a reconnect if required and possible
|
|
#ifdef LIB_SYNCTHING_CONNECTOR_SUPPORT_SYSTEMD
|
|
const auto systemdStatus = applySystemdSettings(reconnectRequired);
|
|
const auto launcherStatus = applyLauncherSettings(reconnectRequired, systemdStatus.consideredForReconnect, systemdStatus.showStartStopButton);
|
|
const auto showStartStopButton = systemdStatus.showStartStopButton || launcherStatus.showStartStopButton;
|
|
const auto systemdOrLauncherRelevantForReconnect = systemdStatus.relevant || launcherStatus.relevant;
|
|
#else
|
|
const auto launcherStatus = applyLauncherSettings(reconnectRequired);
|
|
const auto showStartStopButton = launcherStatus.showStartStopButton;
|
|
const auto systemdOrLauncherRelevantForReconnect = launcherStatus.relevant;
|
|
#endif
|
|
m_ui->startStopPushButton->setVisible(showStartStopButton);
|
|
if (reconnectRequired && !systemdOrLauncherRelevantForReconnect) {
|
|
// simply enforce the reconnect for this connection if the systemd or launcher status are relevant for it
|
|
m_connection.reconnect();
|
|
}
|
|
|
|
#ifndef SYNCTHINGWIDGETS_NO_WEBVIEW
|
|
// web view
|
|
if (m_webViewDlg) {
|
|
m_webViewDlg->applySettings(*m_selectedConnection, false);
|
|
}
|
|
#endif
|
|
|
|
// update visual appearance
|
|
m_ui->trafficFormWidget->setVisible(settings.appearance.showTraffic);
|
|
m_ui->trafficHorizontalSpacer->changeSize(
|
|
0, 20, settings.appearance.showTraffic ? QSizePolicy::Expanding : QSizePolicy::Ignored, QSizePolicy::Minimum);
|
|
if (settings.appearance.showTraffic) {
|
|
updateTraffic();
|
|
}
|
|
m_ui->infoFrame->setFrameStyle(settings.appearance.frameStyle);
|
|
m_ui->buttonsFrame->setFrameStyle(settings.appearance.frameStyle);
|
|
if (QApplication::style() && !QApplication::style()->objectName().compare(QLatin1String("adwaita"), Qt::CaseInsensitive)) {
|
|
m_cornerFrame->setFrameStyle(QFrame::NoFrame);
|
|
} else {
|
|
m_cornerFrame->setFrameStyle(settings.appearance.frameStyle);
|
|
}
|
|
if (settings.appearance.tabPosition >= QTabWidget::North && settings.appearance.tabPosition <= QTabWidget::East) {
|
|
m_ui->tabWidget->setTabPosition(static_cast<QTabWidget::TabPosition>(settings.appearance.tabPosition));
|
|
}
|
|
const auto brightColors = settings.appearance.brightTextColors;
|
|
m_dirModel.setBrightColors(brightColors);
|
|
m_devModel.setBrightColors(brightColors);
|
|
m_dlModel.setBrightColors(brightColors);
|
|
m_recentChangesModel.setBrightColors(brightColors);
|
|
IconManager::instance().applySettings(&settings.icons.status, settings.icons.distinguishTrayIcons ? &settings.icons.tray : nullptr);
|
|
m_ui->webUiPushButton->setIcon(statusIcons().idling);
|
|
|
|
// update status icon and text of tray icon because reconnect interval might have changed
|
|
if (m_menu && m_menu->icon()) {
|
|
m_menu->icon()->updateStatusIconAndText();
|
|
}
|
|
|
|
// show warning when explicitely specified connection configuration was not found
|
|
if (!specifiedConnectionConfigFound && !connectionConfig.isEmpty()) {
|
|
auto *const msgBox = new QMessageBox(QMessageBox::Warning, QCoreApplication::applicationName(),
|
|
tr("The specified connection configuration <em>%1</em> is not defined and hence ignored.").arg(connectionConfig));
|
|
msgBox->setAttribute(Qt::WA_DeleteOnClose);
|
|
msgBox->show();
|
|
}
|
|
}
|
|
|
|
void TrayWidget::applySettingsOnAllInstances()
|
|
{
|
|
for (TrayWidget *instance : s_instances) {
|
|
instance->applySettings();
|
|
}
|
|
}
|
|
|
|
void TrayWidget::openDir(const SyncthingDir &dir)
|
|
{
|
|
if (QDir(dir.path).exists()) {
|
|
openLocalFileOrDir(dir.path);
|
|
} else {
|
|
QMessageBox::warning(
|
|
this, QCoreApplication::applicationName(), tr("The directory <i>%1</i> does not exist on the local machine.").arg(dir.path));
|
|
}
|
|
}
|
|
|
|
void TrayWidget::openItemDir(const SyncthingItemDownloadProgress &item)
|
|
{
|
|
const QDir containingDir(item.fileInfo.absoluteDir());
|
|
if (containingDir.exists()) {
|
|
openLocalFileOrDir(containingDir.path());
|
|
} else {
|
|
QMessageBox::warning(this, QCoreApplication::applicationName(),
|
|
tr("The containing directory <i>%1</i> does not exist on the local machine.").arg(item.fileInfo.filePath()));
|
|
}
|
|
}
|
|
|
|
void TrayWidget::scanDir(const SyncthingDir &dir)
|
|
{
|
|
m_connection.rescan(dir.id);
|
|
}
|
|
|
|
void TrayWidget::pauseResumeDev(const SyncthingDev &dev)
|
|
{
|
|
if (dev.paused) {
|
|
m_connection.resumeDevice(QStringList(dev.id));
|
|
} else {
|
|
m_connection.pauseDevice(QStringList(dev.id));
|
|
}
|
|
}
|
|
|
|
void TrayWidget::pauseResumeDir(const SyncthingDir &dir)
|
|
{
|
|
if (dir.paused) {
|
|
m_connection.resumeDirectories(QStringList(dir.id));
|
|
} else {
|
|
m_connection.pauseDirectories(QStringList(dir.id));
|
|
}
|
|
}
|
|
|
|
void TrayWidget::showRecentChangesContextMenu(const QPoint &position)
|
|
{
|
|
const auto *const selectionModel = m_ui->recentChangesTreeView->selectionModel();
|
|
if (!selectionModel || selectionModel->selectedRows().size() != 1) {
|
|
return;
|
|
}
|
|
const auto copyRole = [this](SyncthingRecentChangesModel::SyncthingRecentChangesModelRole role) {
|
|
return [this, role] {
|
|
const auto *const selectionModel = m_ui->recentChangesTreeView->selectionModel();
|
|
if (selectionModel && selectionModel->selectedRows().size() == 1) {
|
|
QGuiApplication::clipboard()->setText(m_recentChangesModel.data(selectionModel->selectedRows().at(0), role).toString());
|
|
}
|
|
};
|
|
};
|
|
QMenu menu(this);
|
|
connect(menu.addAction(QIcon::fromTheme(QStringLiteral("edit-copy"), QIcon(QStringLiteral(":/icons/hicolor/scalable/actions/edit-copy.svg"))),
|
|
tr("Copy path")),
|
|
&QAction::triggered, this, copyRole(SyncthingRecentChangesModel::Path));
|
|
connect(menu.addAction(QIcon::fromTheme(QStringLiteral("network-server-symbolic"),
|
|
QIcon(QStringLiteral(":/icons/hicolor/scalable/places/network-workgroup.svg"))),
|
|
tr("Copy device ID")),
|
|
&QAction::triggered, this, copyRole(SyncthingRecentChangesModel::ModifiedBy));
|
|
showViewMenu(position, *m_ui->recentChangesTreeView, menu);
|
|
}
|
|
|
|
void TrayWidget::changeStatus()
|
|
{
|
|
switch (m_connection.status()) {
|
|
case SyncthingStatus::Disconnected:
|
|
m_connection.connect();
|
|
break;
|
|
case SyncthingStatus::Reconnecting:
|
|
break;
|
|
case SyncthingStatus::Idle:
|
|
case SyncthingStatus::Scanning:
|
|
case SyncthingStatus::Synchronizing:
|
|
case SyncthingStatus::RemoteNotInSync:
|
|
m_connection.pauseAllDevs();
|
|
break;
|
|
case SyncthingStatus::Paused:
|
|
m_connection.resumeAllDevs();
|
|
break;
|
|
default:;
|
|
}
|
|
}
|
|
|
|
void TrayWidget::updateTraffic()
|
|
{
|
|
if (m_ui->trafficFormWidget->isHidden()) {
|
|
return;
|
|
}
|
|
|
|
// render traffic icons the first time the function is called
|
|
static const auto trafficIcons = [this]() {
|
|
const auto size = QSize(16, 13);
|
|
const auto &palette = m_ui->trafficFormWidget->palette(); // FIXME: make this aware of palette changes
|
|
const auto colorBackground = palette.color(QPalette::Window);
|
|
const auto colorActive = palette.color(QPalette::WindowText);
|
|
const auto colorInactive = QColor((colorActive.red() + colorBackground.red()) / 2, (colorActive.green() + colorBackground.green()) / 2,
|
|
(colorActive.blue() + colorBackground.blue()) / 2);
|
|
const auto renderIcon
|
|
= [&size](const QString &name, const QColor &color) { return Data::renderSvgImage(Data::loadFontAwesomeIcon(name, color), size); };
|
|
struct {
|
|
QPixmap uploadIconActive;
|
|
QPixmap uploadIconInactive;
|
|
QPixmap downloadIconActive;
|
|
QPixmap downloadIconInactive;
|
|
} icons;
|
|
icons.uploadIconActive = renderIcon(QStringLiteral("cloud-upload-alt"), colorActive);
|
|
icons.uploadIconInactive = renderIcon(QStringLiteral("cloud-upload-alt"), colorInactive);
|
|
icons.downloadIconActive = renderIcon(QStringLiteral("cloud-download-alt"), colorActive);
|
|
icons.downloadIconInactive = renderIcon(QStringLiteral("cloud-download-alt"), colorInactive);
|
|
return icons;
|
|
}();
|
|
|
|
// update text and whether to use active/inactive icons
|
|
m_ui->inTrafficLabel->setText(trafficString(m_connection.totalIncomingTraffic(), m_connection.totalIncomingRate()));
|
|
m_ui->outTrafficLabel->setText(trafficString(m_connection.totalOutgoingTraffic(), m_connection.totalOutgoingRate()));
|
|
m_ui->trafficInTextLabel->setPixmap(m_connection.totalIncomingRate() > 0.0 ? trafficIcons.downloadIconActive : trafficIcons.downloadIconInactive);
|
|
m_ui->trafficOutTextLabel->setPixmap(m_connection.totalOutgoingRate() > 0.0 ? trafficIcons.uploadIconActive : trafficIcons.uploadIconInactive);
|
|
}
|
|
|
|
void TrayWidget::updateOverallStatistics()
|
|
{
|
|
const auto overallStats = m_connection.computeOverallDirStatistics();
|
|
m_ui->globalStatisticsLabel->setText(directoryStatusString(overallStats.global));
|
|
m_ui->localStatisticsLabel->setText(directoryStatusString(overallStats.local));
|
|
}
|
|
|
|
void TrayWidget::updateIconAndTooltip()
|
|
{
|
|
if (!m_menu) {
|
|
return;
|
|
}
|
|
if (auto *const trayIcon = m_menu->icon()) {
|
|
trayIcon->updateStatusIconAndText();
|
|
}
|
|
}
|
|
|
|
void TrayWidget::toggleRunning()
|
|
{
|
|
switch (m_startStopButtonTarget) {
|
|
#ifdef LIB_SYNCTHING_CONNECTOR_SUPPORT_SYSTEMD
|
|
case StartStopButtonTarget::Service:
|
|
if (auto *const service = SyncthingService::mainInstance()) {
|
|
service->toggleRunning();
|
|
}
|
|
break;
|
|
#endif
|
|
case StartStopButtonTarget::Launcher:
|
|
if (auto *const launcher = SyncthingLauncher::mainInstance()) {
|
|
if (launcher->isRunning()) {
|
|
launcher->terminate();
|
|
} else {
|
|
launcher->launch(Settings::values().launcher);
|
|
}
|
|
}
|
|
break;
|
|
default:;
|
|
}
|
|
}
|
|
|
|
Settings::Launcher::LauncherStatus TrayWidget::handleLauncherStatusChanged()
|
|
{
|
|
#ifdef LIB_SYNCTHING_CONNECTOR_SUPPORT_SYSTEMD
|
|
const auto systemdStatus = Settings::values().systemd.status(m_connection);
|
|
const auto launcherStatus = applyLauncherSettings(false, systemdStatus.consideredForReconnect, systemdStatus.showStartStopButton);
|
|
const auto showStartStopButton = systemdStatus.showStartStopButton || launcherStatus.showStartStopButton;
|
|
#else
|
|
const auto launcherStatus = applyLauncherSettings(false);
|
|
const auto showStartStopButton = launcherStatus.showStartStopButton;
|
|
#endif
|
|
m_ui->startStopPushButton->setVisible(showStartStopButton);
|
|
return launcherStatus;
|
|
}
|
|
|
|
Settings::Launcher::LauncherStatus TrayWidget::applyLauncherSettings(bool reconnectRequired, bool skipApplyingToConnection, bool skipStartStopButton)
|
|
{
|
|
// update connection
|
|
const auto &launcherSettings = Settings::values().launcher;
|
|
const auto launcherStatus = skipApplyingToConnection ? launcherSettings.status(m_connection)
|
|
: launcherSettings.apply(m_connection, m_selectedConnection, reconnectRequired);
|
|
|
|
if (skipStartStopButton || !launcherStatus.showStartStopButton) {
|
|
return launcherStatus;
|
|
}
|
|
|
|
// update start/stop button
|
|
m_startStopButtonTarget = StartStopButtonTarget::Launcher;
|
|
if (launcherStatus.running) {
|
|
m_ui->startStopPushButton->setText(tr("Stop"));
|
|
m_ui->startStopPushButton->setToolTip(tr("Stop Syncthing instance launched via tray icon"));
|
|
m_ui->startStopPushButton->setIcon(
|
|
QIcon::fromTheme(QStringLiteral("process-stop"), QIcon(QStringLiteral(":/icons/hicolor/scalable/actions/process-stop.svg"))));
|
|
} else {
|
|
m_ui->startStopPushButton->setText(tr("Start"));
|
|
m_ui->startStopPushButton->setToolTip(tr("Start Syncthing with the built-in launcher configured in the settings"));
|
|
m_ui->startStopPushButton->setIcon(
|
|
QIcon::fromTheme(QStringLiteral("system-run"), QIcon(QStringLiteral(":/icons/hicolor/scalable/apps/system-run.svg"))));
|
|
}
|
|
return launcherStatus;
|
|
}
|
|
|
|
#ifdef LIB_SYNCTHING_CONNECTOR_SUPPORT_SYSTEMD
|
|
Settings::Systemd::ServiceStatus TrayWidget::handleSystemdStatusChanged()
|
|
{
|
|
const auto systemdStatus = applySystemdSettings();
|
|
if (systemdStatus.showStartStopButton) {
|
|
m_ui->startStopPushButton->setVisible(true);
|
|
return systemdStatus;
|
|
}
|
|
|
|
// update the start/stop button which might now control the internal launcher
|
|
const auto launcherStatus = applyLauncherSettings(false, true, false);
|
|
m_ui->startStopPushButton->setVisible(launcherStatus.showStartStopButton);
|
|
|
|
return systemdStatus;
|
|
}
|
|
|
|
Settings::Systemd::ServiceStatus TrayWidget::applySystemdSettings(bool reconnectRequired)
|
|
{
|
|
// update connection
|
|
const auto &systemdSettings = Settings::values().systemd;
|
|
const auto serviceStatus = systemdSettings.apply(m_connection, m_selectedConnection, reconnectRequired);
|
|
|
|
if (!serviceStatus.showStartStopButton) {
|
|
return serviceStatus;
|
|
}
|
|
|
|
// update start/stop button
|
|
m_startStopButtonTarget = StartStopButtonTarget::Service;
|
|
if (serviceStatus.running) {
|
|
m_ui->startStopPushButton->setText(tr("Stop"));
|
|
m_ui->startStopPushButton->setToolTip(
|
|
(serviceStatus.userService ? QStringLiteral("systemctl --user stop ") : QStringLiteral("systemctl stop "))
|
|
+ systemdSettings.syncthingUnit);
|
|
m_ui->startStopPushButton->setIcon(
|
|
QIcon::fromTheme(QStringLiteral("process-stop"), QIcon(QStringLiteral(":/icons/hicolor/scalable/actions/process-stop.svg"))));
|
|
} else {
|
|
m_ui->startStopPushButton->setText(tr("Start"));
|
|
m_ui->startStopPushButton->setToolTip(
|
|
(serviceStatus.userService ? QStringLiteral("systemctl --user start ") : QStringLiteral("systemctl start "))
|
|
+ systemdSettings.syncthingUnit);
|
|
m_ui->startStopPushButton->setIcon(
|
|
QIcon::fromTheme(QStringLiteral("system-run"), QIcon(QStringLiteral(":/icons/hicolor/scalable/apps/system-run.svg"))));
|
|
}
|
|
return serviceStatus;
|
|
}
|
|
#endif
|
|
|
|
#ifndef SYNCTHINGWIDGETS_NO_WEBVIEW
|
|
void TrayWidget::handleWebViewDeleted()
|
|
{
|
|
m_webViewDlg = nullptr;
|
|
}
|
|
#endif
|
|
|
|
void TrayWidget::handleNewNotification(DateTime when, const QString &msg)
|
|
{
|
|
m_notifications.emplace_back(QString::fromLocal8Bit(when.toString(DateTimeOutputFormat::DateAndTime, true).data()), msg);
|
|
m_ui->notificationsPushButton->setHidden(false);
|
|
}
|
|
|
|
void TrayWidget::handleConnectionSelected(QAction *connectionAction)
|
|
{
|
|
int index = m_connectionsMenu->actions().indexOf(connectionAction);
|
|
if (index >= 0) {
|
|
m_selectedConnection
|
|
= (index == 0) ? &Settings::values().connection.primary : &Settings::values().connection.secondary[static_cast<size_t>(index - 1)];
|
|
m_ui->connectionsPushButton->setText(m_selectedConnection->label);
|
|
m_connection.reconnect(*m_selectedConnection);
|
|
#ifdef LIB_SYNCTHING_CONNECTOR_SUPPORT_SYSTEMD
|
|
handleSystemdStatusChanged();
|
|
#endif
|
|
#ifndef SYNCTHINGWIDGETS_NO_WEBVIEW
|
|
if (m_webViewDlg) {
|
|
m_webViewDlg->applySettings(*m_selectedConnection, false);
|
|
}
|
|
#endif
|
|
}
|
|
}
|
|
|
|
void TrayWidget::showDialog(QWidget *dlg)
|
|
{
|
|
if (m_menu) {
|
|
m_menu->close();
|
|
}
|
|
dlg->show();
|
|
dlg->activateWindow();
|
|
}
|
|
|
|
} // namespace QtGui
|