syncthingtray/syncthingwidgets/settings/settingsdialog.cpp
Martchus b4879bb23e Hide/show advanced configuration properly using setRowVisible()
This makes the code simpler and avoids inconsistent spacing. Unfortunately
this function has only been introduced in Qt 6.4 so the old code has to
stay for older Qt versions.
2023-11-06 16:02:12 +01:00

1752 lines
71 KiB
C++

#include "./settingsdialog.h"
#include "./settings.h"
#include "./wizard.h"
#include "../misc/syncthinglauncher.h"
#include <qtutilities/misc/compat.h>
#include <syncthingconnector/syncthingconfig.h>
#include <syncthingconnector/syncthingconnection.h>
#include <syncthingconnector/syncthingprocess.h>
#include <syncthingconnector/utils.h>
#include <syncthingmodel/syncthingstatuscomputionmodel.h>
#ifdef LIB_SYNCTHING_CONNECTOR_SUPPORT_SYSTEMD
#include <syncthingconnector/syncthingservice.h>
#include <syncthingmodel/colors.h>
#include <syncthingmodel/syncthingicons.h>
#endif
#include "ui_appearanceoptionpage.h"
#include "ui_autostartoptionpage.h"
#include "ui_connectionoptionpage.h"
#include "ui_iconsoptionpage.h"
#include "ui_launcheroptionpage.h"
#include "ui_notificationsoptionpage.h"
#ifdef LIB_SYNCTHING_CONNECTOR_SUPPORT_SYSTEMD
#include "ui_systemdoptionpage.h"
#endif
#include "ui_builtinwebviewoptionpage.h"
#include "ui_generalwebviewoptionpage.h"
// use meta-data of syncthingtray application here
#include "resources/../../tray/resources/config.h"
#include <qtutilities/misc/compat.h>
#include <qtutilities/misc/desktoputils.h>
#include <qtutilities/misc/dialogutils.h>
#include <qtutilities/paletteeditor/colorbutton.h>
#include <qtutilities/settingsdialog/optioncategory.h>
#include <qtutilities/settingsdialog/optioncategorymodel.h>
#include <qtutilities/settingsdialog/qtsettings.h>
#include <qtutilities/widgets/iconbutton.h>
#ifdef QT_UTILITIES_SUPPORT_DBUS_NOTIFICATIONS
#include <qtutilities/misc/dbusnotification.h>
#endif
#ifdef LIB_SYNCTHING_CONNECTOR_SUPPORT_SYSTEMD
#include <c++utilities/chrono/datetime.h>
#include <qtutilities/misc/dialogutils.h>
#endif
#include <QDesktopServices>
#include <QFileDialog>
#include <QHostAddress>
#include <QInputDialog>
#include <QMessageBox>
#if defined(PLATFORM_LINUX) && !defined(Q_OS_ANDROID)
#include <QStandardPaths>
#elif defined(PLATFORM_WINDOWS)
#include <QRegularExpression>
#include <QRegularExpressionMatch>
#include <QSettings>
#elif defined(PLATFORM_MAC)
#include <QFileInfo>
#endif
#include <QApplication>
#include <QFontDatabase>
#include <QMenu>
#include <QStringBuilder>
#include <QStyle>
#include <QTextBlock>
#include <QTextCursor>
#include <functional>
#include <initializer_list>
using namespace std;
using namespace std::placeholders;
using namespace Settings;
using namespace Data;
using namespace CppUtilities;
using namespace QtUtilities;
namespace QtGui {
// ConnectionOptionPage
ConnectionOptionPage::ConnectionOptionPage(Data::SyncthingConnection *connection, QWidget *parentWidget)
: ConnectionOptionPageBase(parentWidget)
, m_connection(connection)
, m_currentIndex(0)
{
}
ConnectionOptionPage::~ConnectionOptionPage()
{
}
void ConnectionOptionPage::hideConnectionStatus()
{
ui()->statusTextLabel->setHidden(true);
ui()->statusLabel->setHidden(true);
ui()->connectPushButton->setHidden(true);
m_connection = nullptr;
}
QWidget *ConnectionOptionPage::setupWidget()
{
auto *const widget = ConnectionOptionPageBase::setupWidget();
m_statusComputionModel = new SyncthingStatusComputionModel(widget);
ui()->certPathSelection->provideCustomFileMode(QFileDialog::ExistingFile);
ui()->certPathSelection->lineEdit()->setPlaceholderText(
QCoreApplication::translate("QtGui::ConnectionOptionPage", "Auto-detected for local instance"));
ui()->instanceNoteIcon->setPixmap(QApplication::style()->standardIcon(QStyle::SP_MessageBoxInformation).pixmap(32, 32));
ui()->pollTrafficLabel->setToolTip(ui()->pollTrafficSpinBox->toolTip());
ui()->pollDevStatsLabel->setToolTip(ui()->pollDevStatsSpinBox->toolTip());
ui()->pollErrorsLabel->setToolTip(ui()->pollErrorsSpinBox->toolTip());
ui()->reconnectLabel->setToolTip(ui()->reconnectSpinBox->toolTip());
if (m_connection) {
QObject::connect(m_connection, &SyncthingConnection::statusChanged, bind(&ConnectionOptionPage::updateConnectionStatus, this));
} else {
hideConnectionStatus();
}
ui()->statusComputionFlagsListView->setModel(m_statusComputionModel);
QObject::connect(ui()->connectPushButton, &QPushButton::clicked, bind(&ConnectionOptionPage::applyAndReconnect, this));
QObject::connect(ui()->insertFromConfigFilePushButton, &QPushButton::clicked, bind(&ConnectionOptionPage::insertFromConfigFile, this, false));
QObject::connect(
ui()->insertFromCustomConfigFilePushButton, &QPushButton::clicked, bind(&ConnectionOptionPage::insertFromConfigFile, this, true));
QObject::connect(ui()->selectionComboBox, static_cast<void (QComboBox::*)(int)>(&QComboBox::currentIndexChanged),
bind(&ConnectionOptionPage::showConnectionSettings, this, _1));
QObject::connect(ui()->selectionComboBox, static_cast<void (QComboBox::*)(const QString &)>(&QComboBox::editTextChanged),
bind(&ConnectionOptionPage::saveCurrentConfigName, this, _1));
QObject::connect(ui()->downPushButton, &QPushButton::clicked, bind(&ConnectionOptionPage::moveSelectedConfigDown, this));
QObject::connect(ui()->upPushButton, &QPushButton::clicked, bind(&ConnectionOptionPage::moveSelectedConfigUp, this));
QObject::connect(ui()->addPushButton, &QPushButton::clicked, bind(&ConnectionOptionPage::addNewConfig, this));
QObject::connect(ui()->removePushButton, &QPushButton::clicked, bind(&ConnectionOptionPage::removeSelectedConfig, this));
QObject::connect(ui()->advancedCheckBox, &QCheckBox::toggled, bind(&ConnectionOptionPage::toggleAdvancedSettings, this, std::placeholders::_1));
#if (QT_VERSION < QT_VERSION_CHECK(5, 15, 0))
ui()->timeoutSpinBox->setEnabled(false);
#endif
toggleAdvancedSettings(false);
return widget;
}
void ConnectionOptionPage::insertFromConfigFile(bool forceFileSelection)
{
auto configFile(forceFileSelection ? QString() : SyncthingConfig::locateConfigFile());
if (configFile.isEmpty()) {
// allow user to select config file manually if it could not be located
configFile = QFileDialog::getOpenFileName(
widget(), QCoreApplication::translate("QtGui::ConnectionOptionPage", "Select Syncthing config file") + QStringLiteral(" - " APP_NAME));
}
if (configFile.isEmpty()) {
return;
}
SyncthingConfig config;
if (!config.restore(configFile)) {
QMessageBox::critical(widget(), widget()->windowTitle() + QStringLiteral(" - " APP_NAME),
QCoreApplication::translate("QtGui::ConnectionOptionPage", "Unable to parse the Syncthing config file."));
return;
}
if (!config.guiAddress.isEmpty()) {
const auto portStart(config.guiAddress.indexOf(QChar(':')));
auto guiHost(config.guiAddress.mid(0, portStart));
const auto guiPort = portStart > 0 ? QtUtilities::midRef(config.guiAddress, portStart) : QtUtilities::StringView();
const QHostAddress guiAddress(guiHost);
// assume local connection if address is eg. 0.0.0.0
auto localConnection = true;
if (guiAddress == QHostAddress::AnyIPv4) {
guiHost = QStringLiteral("127.0.0.1");
} else if (guiAddress == QHostAddress::AnyIPv6) {
guiHost = QStringLiteral("[::1]");
} else if (!isLocal(guiHost, guiAddress)) {
localConnection = false;
}
const QString guiProtocol((config.guiEnforcesSecureConnection || !localConnection) ? QStringLiteral("https://") : QStringLiteral("http://"));
ui()->urlLineEdit->selectAll();
ui()->urlLineEdit->insert(guiProtocol % guiHost % guiPort);
}
if (!config.guiUser.isEmpty() || !config.guiPasswordHash.isEmpty()) {
ui()->authCheckBox->setChecked(true);
ui()->userNameLineEdit->selectAll();
ui()->userNameLineEdit->insert(config.guiUser);
} else {
ui()->authCheckBox->setChecked(false);
}
if (!config.guiApiKey.isEmpty()) {
ui()->apiKeyLineEdit->selectAll();
ui()->apiKeyLineEdit->insert(config.guiApiKey);
}
}
void ConnectionOptionPage::updateConnectionStatus()
{
if (m_connection) {
ui()->statusLabel->setText(m_connection->statusText());
}
}
bool ConnectionOptionPage::showConnectionSettings(int index)
{
if (index == m_currentIndex) {
return true;
}
if (!cacheCurrentSettings(false)) {
ui()->selectionComboBox->setCurrentIndex(m_currentIndex);
return false;
}
const SyncthingConnectionSettings &connectionSettings = (index == 0 ? m_primarySettings : m_secondarySettings[static_cast<size_t>(index - 1)]);
ui()->urlLineEdit->setText(connectionSettings.syncthingUrl);
ui()->authCheckBox->setChecked(connectionSettings.authEnabled);
ui()->userNameLineEdit->setText(connectionSettings.userName);
ui()->passwordLineEdit->setText(connectionSettings.password);
ui()->apiKeyLineEdit->setText(connectionSettings.apiKey);
ui()->certPathSelection->lineEdit()->setText(connectionSettings.httpsCertPath);
ui()->timeoutSpinBox->setValue(connectionSettings.requestTimeout);
ui()->longPollingSpinBox->setValue(connectionSettings.longPollingTimeout);
ui()->pollTrafficSpinBox->setValue(connectionSettings.trafficPollInterval);
ui()->pollDevStatsSpinBox->setValue(connectionSettings.devStatsPollInterval);
ui()->pollErrorsSpinBox->setValue(connectionSettings.errorsPollInterval);
ui()->reconnectSpinBox->setValue(connectionSettings.reconnectInterval);
ui()->autoConnectCheckBox->setChecked(connectionSettings.autoConnect);
m_statusComputionModel->setStatusComputionFlags(connectionSettings.statusComputionFlags);
setCurrentIndex(index);
return true;
}
bool ConnectionOptionPage::cacheCurrentSettings(bool applying)
{
if (m_currentIndex < 0) {
return true;
}
SyncthingConnectionSettings &connectionSettings
= (m_currentIndex == 0 ? m_primarySettings : m_secondarySettings[static_cast<size_t>(m_currentIndex - 1)]);
connectionSettings.syncthingUrl = ui()->urlLineEdit->text();
connectionSettings.authEnabled = ui()->authCheckBox->isChecked();
connectionSettings.userName = ui()->userNameLineEdit->text();
connectionSettings.password = ui()->passwordLineEdit->text();
connectionSettings.apiKey = ui()->apiKeyLineEdit->text().toUtf8();
connectionSettings.expectedSslErrors.clear();
connectionSettings.httpsCertPath = ui()->certPathSelection->lineEdit()->text();
connectionSettings.requestTimeout = ui()->timeoutSpinBox->value();
connectionSettings.longPollingTimeout = ui()->longPollingSpinBox->value();
connectionSettings.trafficPollInterval = ui()->pollTrafficSpinBox->value();
connectionSettings.devStatsPollInterval = ui()->pollDevStatsSpinBox->value();
connectionSettings.errorsPollInterval = ui()->pollErrorsSpinBox->value();
connectionSettings.reconnectInterval = ui()->reconnectSpinBox->value();
connectionSettings.autoConnect = ui()->autoConnectCheckBox->isChecked();
connectionSettings.statusComputionFlags = m_statusComputionModel->statusComputionFlags();
if (!connectionSettings.loadHttpsCert()) {
const QString errorMessage = QCoreApplication::translate("QtGui::ConnectionOptionPage", "Unable to load specified certificate \"%1\".")
.arg(connectionSettings.httpsCertPath);
if (!applying) {
QMessageBox::critical(widget(), QCoreApplication::applicationName(), errorMessage);
} else {
errors() << errorMessage;
}
return false;
}
return true;
}
void ConnectionOptionPage::saveCurrentConfigName(const QString &name)
{
const int index = ui()->selectionComboBox->currentIndex();
if (index == m_currentIndex && index >= 0) {
(index == 0 ? m_primarySettings : m_secondarySettings[static_cast<size_t>(index - 1)]).label = name;
ui()->selectionComboBox->setItemText(index, name);
}
}
void ConnectionOptionPage::addNewConfig()
{
m_secondarySettings.emplace_back();
m_secondarySettings.back().label
= QCoreApplication::translate("QtGui::ConnectionOptionPage", "Instance %1").arg(ui()->selectionComboBox->count() + 1);
ui()->selectionComboBox->addItem(m_secondarySettings.back().label);
ui()->selectionComboBox->setCurrentIndex(ui()->selectionComboBox->count() - 1);
ui()->removePushButton->setEnabled(true);
}
void ConnectionOptionPage::removeSelectedConfig()
{
if (m_secondarySettings.empty()) {
return;
}
const int index = ui()->selectionComboBox->currentIndex();
if (index < 0 || static_cast<unsigned>(index) > m_secondarySettings.size()) {
return;
}
if (index == 0) {
m_primarySettings = std::move(m_secondarySettings.front());
m_secondarySettings.erase(m_secondarySettings.begin());
} else {
m_secondarySettings.erase(m_secondarySettings.begin() + (index - 1));
}
m_currentIndex = -1;
ui()->selectionComboBox->removeItem(index);
ui()->removePushButton->setEnabled(!m_secondarySettings.empty());
}
void ConnectionOptionPage::moveSelectedConfigDown()
{
if (m_secondarySettings.empty()) {
return;
}
const int index = ui()->selectionComboBox->currentIndex();
if (index < 0) {
return;
}
if (index == 0) {
swap(m_primarySettings, m_secondarySettings.front());
ui()->selectionComboBox->setItemText(0, m_primarySettings.label);
ui()->selectionComboBox->setItemText(1, m_secondarySettings.front().label);
setCurrentIndex(1);
} else if (static_cast<unsigned>(index) < m_secondarySettings.size()) {
SyncthingConnectionSettings &current = m_secondarySettings[static_cast<unsigned>(index) - 1];
SyncthingConnectionSettings &exchange = m_secondarySettings[static_cast<unsigned>(index)];
swap(current, exchange);
ui()->selectionComboBox->setItemText(index, current.label);
ui()->selectionComboBox->setItemText(index + 1, exchange.label);
setCurrentIndex(index + 1);
}
ui()->selectionComboBox->setCurrentIndex(m_currentIndex);
}
void ConnectionOptionPage::moveSelectedConfigUp()
{
if (m_secondarySettings.empty()) {
return;
}
const int index = ui()->selectionComboBox->currentIndex();
if (index <= 0) {
return;
}
if (index == 1) {
swap(m_primarySettings, m_secondarySettings.front());
ui()->selectionComboBox->setItemText(0, m_primarySettings.label);
ui()->selectionComboBox->setItemText(1, m_secondarySettings.front().label);
setCurrentIndex(0);
} else if (static_cast<unsigned>(index) - 1 < m_secondarySettings.size()) {
SyncthingConnectionSettings &current = m_secondarySettings[static_cast<unsigned>(index) - 1];
SyncthingConnectionSettings &exchange = m_secondarySettings[static_cast<unsigned>(index) - 2];
swap(current, exchange);
ui()->selectionComboBox->setItemText(index, current.label);
ui()->selectionComboBox->setItemText(index - 1, exchange.label);
setCurrentIndex(index - 1);
}
ui()->selectionComboBox->setCurrentIndex(m_currentIndex);
}
void ConnectionOptionPage::setCurrentIndex(int currentIndex)
{
m_currentIndex = currentIndex;
ui()->downPushButton->setEnabled(currentIndex >= 0 && static_cast<unsigned>(currentIndex) < m_secondarySettings.size());
ui()->upPushButton->setEnabled(currentIndex > 0 && static_cast<unsigned>(currentIndex) - 1 < m_secondarySettings.size());
}
void ConnectionOptionPage::toggleAdvancedSettings(bool show)
{
if (!ui()) {
return;
}
#if QT_VERSION >= QT_VERSION_CHECK(6, 4, 0)
for (auto *const widget : std::initializer_list<QWidget *>{
ui()->authLabel, ui()->userNameLabel, ui()->passwordLabel, ui()->timeoutLabel, ui()->longPollingLabel, ui()->pollLabel }) {
ui()->formLayout->setRowVisible(widget, show);
}
#else
for (auto *const widget : std::initializer_list<QWidget *>{ ui()->authLabel, ui()->authCheckBox, ui()->userNameLabel, ui()->userNameLineEdit,
ui()->passwordLabel, ui()->passwordLineEdit, ui()->timeoutLabel, ui()->timeoutSpinBox, ui()->longPollingLabel, ui()->longPollingSpinBox,
ui()->pollLabel, ui()->pollDevStatsLabel, ui()->pollDevStatsSpinBox, ui()->pollErrorsLabel, ui()->pollErrorsSpinBox,
ui()->pollTrafficLabel, ui()->pollTrafficSpinBox, ui()->reconnectLabel, ui()->reconnectSpinBox }) {
widget->setVisible(show);
}
#endif
}
bool ConnectionOptionPage::apply()
{
if (!cacheCurrentSettings(true)) {
return false;
}
values().connection.primary = m_primarySettings;
values().connection.secondary = m_secondarySettings;
return true;
}
void ConnectionOptionPage::reset()
{
m_primarySettings = values().connection.primary;
m_secondarySettings = values().connection.secondary;
m_currentIndex = -1;
QStringList itemTexts;
itemTexts.reserve(1 + static_cast<int>(m_secondarySettings.size()));
itemTexts << m_primarySettings.label;
for (const SyncthingConnectionSettings &settings : m_secondarySettings) {
itemTexts << settings.label;
}
ui()->selectionComboBox->clear();
ui()->selectionComboBox->addItems(itemTexts);
ui()->selectionComboBox->setCurrentIndex(0);
updateConnectionStatus();
}
void ConnectionOptionPage::applyAndReconnect()
{
apply();
if (m_connection) {
m_connection->reconnect((m_currentIndex == 0 ? m_primarySettings : m_secondarySettings[static_cast<size_t>(m_currentIndex - 1)]));
}
}
// NotificationsOptionPage
NotificationsOptionPage::NotificationsOptionPage(GuiType guiType, QWidget *parentWidget)
: NotificationsOptionPageBase(parentWidget)
, m_guiType(guiType)
{
}
NotificationsOptionPage::~NotificationsOptionPage()
{
}
QWidget *NotificationsOptionPage::setupWidget()
{
auto *const widget = NotificationsOptionPageBase::setupWidget();
switch (m_guiType) {
case GuiType::TrayWidget:
break;
case GuiType::Plasmoid:
ui()->apiGroupBox->setHidden(true);
break;
}
return widget;
}
bool NotificationsOptionPage::apply()
{
bool ok = true;
auto &settings(values());
auto &notifyOn(settings.notifyOn);
notifyOn.disconnect = ui()->notifyOnDisconnectCheckBox->isChecked();
notifyOn.internalErrors = ui()->notifyOnErrorsCheckBox->isChecked();
notifyOn.launcherErrors = ui()->notifyOnLauncherErrorsCheckBox->isChecked();
notifyOn.localSyncComplete = ui()->notifyOnLocalSyncCompleteCheckBox->isChecked();
notifyOn.remoteSyncComplete = ui()->notifyOnRemoteSyncCompleteCheckBox->isChecked();
notifyOn.syncthingErrors = ui()->showSyncthingNotificationsCheckBox->isChecked();
notifyOn.newDeviceConnects = ui()->notifyOnNewDevConnectsCheckBox->isChecked();
notifyOn.newDirectoryShared = ui()->notifyOnNewDirSharedCheckBox->isChecked();
#ifdef QT_UTILITIES_SUPPORT_DBUS_NOTIFICATIONS
if ((settings.dbusNotifications = ui()->dbusRadioButton->isChecked()) && !DBusNotification::isAvailable()) {
errors() << QCoreApplication::translate(
"QtGui::NotificationsOptionPage", "Configured to use D-Bus notifications but D-Bus notification daemon seems unavailabe.");
ok = false;
}
#endif
values().ignoreInavailabilityAfterStart = static_cast<unsigned int>(ui()->ignoreInavailabilityAfterStartSpinBox->value());
return ok;
}
void NotificationsOptionPage::reset()
{
const auto &notifyOn = values().notifyOn;
ui()->notifyOnDisconnectCheckBox->setChecked(notifyOn.disconnect);
ui()->notifyOnErrorsCheckBox->setChecked(notifyOn.internalErrors);
ui()->notifyOnLauncherErrorsCheckBox->setChecked(notifyOn.launcherErrors);
ui()->notifyOnLocalSyncCompleteCheckBox->setChecked(notifyOn.localSyncComplete);
ui()->notifyOnRemoteSyncCompleteCheckBox->setChecked(notifyOn.remoteSyncComplete);
ui()->showSyncthingNotificationsCheckBox->setChecked(notifyOn.syncthingErrors);
ui()->notifyOnNewDevConnectsCheckBox->setChecked(notifyOn.newDeviceConnects);
ui()->notifyOnNewDirSharedCheckBox->setChecked(notifyOn.newDirectoryShared);
#ifdef QT_UTILITIES_SUPPORT_DBUS_NOTIFICATIONS
(values().dbusNotifications ? ui()->dbusRadioButton : ui()->qtRadioButton)->setChecked(true);
#else
ui()->dbusRadioButton->setEnabled(false);
ui()->qtRadioButton->setChecked(true);
#endif
ui()->ignoreInavailabilityAfterStartSpinBox->setValue(static_cast<int>(values().ignoreInavailabilityAfterStart));
}
// AppearanceOptionPage
AppearanceOptionPage::AppearanceOptionPage(QWidget *parentWidget)
: AppearanceOptionPageBase(parentWidget)
{
}
AppearanceOptionPage::~AppearanceOptionPage()
{
}
bool AppearanceOptionPage::apply()
{
auto &v = values();
auto &settings = v.appearance;
settings.windowType = ui()->windowTypeComboBox->currentIndex();
settings.trayMenuSize.setWidth(ui()->widthSpinBox->value());
settings.trayMenuSize.setHeight(ui()->heightSpinBox->value());
settings.showTraffic = ui()->showTrafficCheckBox->isChecked();
settings.showTabTexts = ui()->showTabTextsCheckBox->isChecked();
v.icons.preferIconsFromTheme = ui()->preferIconsFromThemeCheckBox->isChecked();
int style;
switch (ui()->frameShapeComboBox->currentIndex()) {
case 0:
style = QFrame::NoFrame;
break;
case 1:
style = QFrame::Box;
break;
case 2:
style = QFrame::Panel;
break;
default:
style = QFrame::StyledPanel;
}
switch (ui()->frameShadowComboBox->currentIndex()) {
case 0:
style |= QFrame::Plain;
break;
case 1:
style |= QFrame::Raised;
break;
default:
style |= QFrame::Sunken;
}
settings.frameStyle = style;
settings.tabPosition = ui()->tabPosComboBox->currentIndex();
settings.positioning.useCursorPosition = ui()->useCursorPosCheckBox->isChecked();
settings.positioning.useAssumedIconPosition = ui()->assumeIconPosCheckBox->isChecked();
settings.positioning.assumedIconPosition = QPoint(ui()->xPosSpinBox->value(), ui()->yPosSpinBox->value());
return true;
}
void AppearanceOptionPage::resetPositioningSettings()
{
const auto &v = values();
const auto &settings = v.appearance;
ui()->widthSpinBox->setValue(settings.trayMenuSize.width());
ui()->heightSpinBox->setValue(settings.trayMenuSize.height());
ui()->xPosSpinBox->setValue(settings.positioning.assumedIconPosition.x());
ui()->yPosSpinBox->setValue(settings.positioning.assumedIconPosition.y());
}
void AppearanceOptionPage::reset()
{
const auto &v = values();
const auto &settings = v.appearance;
resetPositioningSettings();
ui()->windowTypeComboBox->setCurrentIndex(settings.windowType);
ui()->showTrafficCheckBox->setChecked(settings.showTraffic);
ui()->showTabTextsCheckBox->setChecked(settings.showTabTexts);
ui()->preferIconsFromThemeCheckBox->setChecked(v.icons.preferIconsFromTheme);
int index;
switch (settings.frameStyle & QFrame::Shape_Mask) {
case QFrame::NoFrame:
index = 0;
break;
case QFrame::Box:
index = 1;
break;
case QFrame::Panel:
index = 2;
break;
default:
index = 3;
}
ui()->frameShapeComboBox->setCurrentIndex(index);
switch (settings.frameStyle & QFrame::Shadow_Mask) {
case QFrame::Plain:
index = 0;
break;
case QFrame::Raised:
index = 1;
break;
default:
index = 2;
}
ui()->frameShadowComboBox->setCurrentIndex(index);
ui()->tabPosComboBox->setCurrentIndex(settings.tabPosition);
ui()->useCursorPosCheckBox->setChecked(settings.positioning.useCursorPosition);
ui()->assumeIconPosCheckBox->setChecked(settings.positioning.useAssumedIconPosition);
}
// IconsOptionPage
IconsOptionPage::IconsOptionPage(Context context, QWidget *parentWidget)
: IconsOptionPageBase(parentWidget)
, m_context(context)
{
}
IconsOptionPage::~IconsOptionPage()
{
}
QWidget *IconsOptionPage::setupWidget()
{
auto *const widget = IconsOptionPageBase::setupWidget();
// set context-specific elements
switch (m_context) {
case Context::Combined:
ui()->contextLabel->hide();
ui()->contextCheckBox->hide();
break;
case Context::UI:
widget->setWindowTitle(QCoreApplication::translate("QtGui::IconsOptionPageBase", "UI icons"));
ui()->contextLabel->setText(
QCoreApplication::translate("QtGui::IconsOptionPageBase", "These icon settings are used within Syncthing Tray's UI."));
ui()->contextCheckBox->hide();
break;
case Context::System:
widget->setWindowTitle(QCoreApplication::translate("QtGui::IconsOptionPageBase", "System icons"));
ui()->contextLabel->setText(QCoreApplication::translate(
"QtGui::IconsOptionPageBase", "These icon settings are used for the system tray icon and the notifications."));
ui()->contextCheckBox->setText(QCoreApplication::translate("QtGui::IconsOptionPageBase", "Use same settings as for UI icons"));
break;
}
// allow changing stroke thickness
QObject::connect(ui()->thickStrokeWidthCheckBox, &QCheckBox::toggled, widget,
[this](bool thick) { m_settings.strokeWidth = thick ? StatusIconStrokeWidth::Thick : StatusIconStrokeWidth::Normal; });
// populate form for status icon colors
auto *const gridLayout = ui()->gridLayout;
auto *const statusIconsGroupBox = ui()->statusIconsGroupBox;
int index = 0;
for (auto &colorMapping : m_settings.colorMapping()) {
// populate widgets array
auto &widgetsForColor = m_widgets[index++] = {
{
new ColorButton(statusIconsGroupBox),
new ColorButton(statusIconsGroupBox),
new ColorButton(statusIconsGroupBox),
},
new QLabel(statusIconsGroupBox),
&colorMapping.setting,
colorMapping.defaultEmblem,
};
widgetsForColor.previewLabel->setMaximumSize(QSize(32, 32));
// add label for color name
gridLayout->addWidget(new QLabel(colorMapping.colorName, statusIconsGroupBox), index, 0, Qt::AlignRight | Qt::AlignVCenter);
// setup preview
gridLayout->addWidget(widgetsForColor.previewLabel, index, 4, Qt::AlignCenter);
const auto updatePreview = [this, &widgetsForColor] {
widgetsForColor.previewLabel->setPixmap(renderSvgImage(makeSyncthingIcon(
StatusIconColorSet{
widgetsForColor.colorButtons[0]->color(),
widgetsForColor.colorButtons[1]->color(),
widgetsForColor.colorButtons[2]->color(),
},
widgetsForColor.statusEmblem, m_settings.strokeWidth),
widgetsForColor.previewLabel->maximumSize()));
};
for (const auto &colorButton : widgetsForColor.colorButtons) {
QObject::connect(colorButton, &ColorButton::colorChanged, widget, updatePreview);
}
QObject::connect(ui()->thickStrokeWidthCheckBox, &QCheckBox::toggled, widget, updatePreview);
// setup color buttons
widgetsForColor.colorButtons[0]->setColor(colorMapping.setting.backgroundStart);
widgetsForColor.colorButtons[1]->setColor(colorMapping.setting.backgroundEnd);
widgetsForColor.colorButtons[2]->setColor(colorMapping.setting.foreground);
gridLayout->addWidget(widgetsForColor.colorButtons[0], index, 1);
gridLayout->addWidget(widgetsForColor.colorButtons[1], index, 2);
gridLayout->addWidget(widgetsForColor.colorButtons[2], index, 3);
if (index >= StatusIconSettings::distinguishableColorCount) {
break;
}
}
// setup presets menu
auto *const presetsMenu = new QMenu(widget);
presetsMenu->addAction(QCoreApplication::translate("QtGui::IconsOptionPageBase", "Colorful background with gradient (default)"), widget, [this] {
m_settings = Data::StatusIconSettings();
update();
});
presetsMenu->addAction(
QCoreApplication::translate("QtGui::IconsOptionPageBase", "Transparent background and dark foreground (for bright themes)"), widget, [this] {
m_settings = Data::StatusIconSettings(Data::StatusIconSettings::BrightTheme{});
update();
});
presetsMenu->addAction(
QCoreApplication::translate("QtGui::IconsOptionPageBase", "Transparent background and bright foreground (for dark themes)"), widget, [this] {
m_settings = Data::StatusIconSettings(Data::StatusIconSettings::DarkTheme{});
update();
});
// setup additional buttons
ui()->restoreDefaultsPushButton->setMenu(presetsMenu);
QObject::connect(ui()->restorePreviousPushButton, &QPushButton::clicked, widget, [this] { reset(); });
// setup slider
QObject::connect(ui()->renderingSizeSlider, &QSlider::valueChanged, widget, [this](int value) {
m_settings.renderSize = QSize(value, value);
auto *const label = ui()->renderingSizeLabel;
if (const auto scaleFactor = label->devicePixelRatioF(); scaleFactor == 1.0) {
label->setText(QString::number(value) + QStringLiteral(" px"));
} else {
label->setText(QCoreApplication::translate("QtGui::IconsOptionPageBase", "%1 px (scaled to %2 px)")
.arg(QString::number(value), QString::number(static_cast<qreal>(value) * scaleFactor, 'f', 0)));
}
});
return widget;
}
bool IconsOptionPage::apply()
{
for (auto &widgetsForColor : m_widgets) {
*widgetsForColor.setting = StatusIconColorSet{
widgetsForColor.colorButtons[0]->color(),
widgetsForColor.colorButtons[1]->color(),
widgetsForColor.colorButtons[2]->color(),
};
}
auto &iconSettings = values().icons;
switch (m_context) {
case Context::Combined:
case Context::UI:
iconSettings.status = m_settings;
break;
case Context::System:
iconSettings.tray = m_settings;
iconSettings.distinguishTrayIcons = !ui()->contextCheckBox->isChecked();
break;
}
return true;
}
void IconsOptionPage::update()
{
ui()->renderingSizeSlider->setValue(std::max(m_settings.renderSize.width(), m_settings.renderSize.height()));
ui()->thickStrokeWidthCheckBox->setChecked(m_settings.strokeWidth == StatusIconStrokeWidth::Thick);
for (auto &widgetsForColor : m_widgets) {
widgetsForColor.colorButtons[0]->setColor(widgetsForColor.setting->backgroundStart);
widgetsForColor.colorButtons[1]->setColor(widgetsForColor.setting->backgroundEnd);
widgetsForColor.colorButtons[2]->setColor(widgetsForColor.setting->foreground);
}
}
void IconsOptionPage::reset()
{
const auto &iconSettings = values().icons;
switch (m_context) {
case Context::Combined:
case Context::UI:
m_settings = iconSettings.status;
break;
case Context::System:
m_settings = iconSettings.tray;
ui()->contextCheckBox->setChecked(!iconSettings.distinguishTrayIcons);
break;
}
update();
}
// AutostartOptionPage
AutostartOptionPage::AutostartOptionPage(QWidget *parentWidget)
: AutostartOptionPageBase(parentWidget)
{
}
AutostartOptionPage::~AutostartOptionPage()
{
}
QWidget *AutostartOptionPage::setupWidget()
{
auto *widget = AutostartOptionPageBase::setupWidget();
auto *style = QApplication::style();
ui()->infoIconLabel->setPixmap(
style->standardIcon(QStyle::SP_MessageBoxInformation, nullptr, ui()->infoIconLabel).pixmap(ui()->infoIconLabel->size()));
ui()->pathWarningIconLabel->setPixmap(
style->standardIcon(QStyle::SP_MessageBoxWarning, nullptr, ui()->pathWarningIconLabel).pixmap(ui()->pathWarningIconLabel->size()));
QObject::connect(ui()->deleteExistingEntryPushButton, &QPushButton::clicked, widget, [this] {
setAutostartPath(QString());
reset();
});
#if defined(PLATFORM_LINUX) && !defined(PLATFORM_ANDROID)
ui()->platformNoteLabel->setText(QCoreApplication::translate("QtGui::AutostartOptionPage",
"This is achieved by adding a *.desktop file under <i>~/.config/autostart</i> so the setting only affects the current user."));
#elif defined(PLATFORM_WINDOWS)
ui()->platformNoteLabel->setText(QCoreApplication::translate("QtGui::AutostartOptionPage",
"This is achieved by adding a registry key under <i>HKEY_CURRENT_USER\\SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\Run</i> so the setting "
"only affects the current user. Note that the startup entry is invalidated when moving <i>syncthingtray.exe</i>."));
#elif defined(PLATFORM_MAC)
ui()->platformNoteLabel->setText(QCoreApplication::translate("QtGui::AutostartOptionPage",
"This is achieved by adding a *.plist file under <i>~/Library/LaunchAgents</i> so the setting only affects the current user."));
#else
ui()->platformNoteLabel->setText(
QCoreApplication::translate("QtGui::AutostartOptionPage", "This feature has not been implemented for your platform (yet)."));
m_unsupported = true;
ui()->pathWidget->setVisible(false);
ui()->autostartCheckBox->setEnabled(false);
#endif
return widget;
}
/*!
* \brief Returns the currently configured autostart path or an empty string if autostart is disabled; returns std::nullopt when the
* path cannot be determined.
* \remarks
* - At this point the path cannot be determined on MacOS.
* - Use isAutostartEnabled() if you just need to know whether autostart is generally enabled as this is a simpler check. This
* function is also generally useful as a fallback.
*/
std::optional<QString> configuredAutostartPath()
{
#if defined(PLATFORM_LINUX) && !defined(Q_OS_ANDROID)
auto desktopFile = QFile(QStandardPaths::locate(QStandardPaths::ConfigLocation, QStringLiteral("autostart/" PROJECT_NAME ".desktop")));
// check whether the file can be opened and whether it is enabled but prevent reading large files
if (!desktopFile.open(QFile::ReadOnly)) {
return QString();
}
if (desktopFile.size() > (5 * 1024)) {
return std::nullopt;
}
const auto data = QString::fromUtf8(desktopFile.readAll());
if (data.contains(QLatin1String("Hidden=true"))) {
return QString();
}
static const auto regex = QRegularExpression(QStringLiteral("Exec=(\"([^\"\\n]*)\"|([^\\s\\n]*))"));
const auto match = regex.match(data);
auto captured = match.captured(2);
if (captured.isNull()) {
captured = match.captured(3);
}
return captured.isNull() ? std::nullopt : std::make_optional(captured);
#elif defined(PLATFORM_WINDOWS)
return QSettings(QStringLiteral("HKEY_CURRENT_USER\\SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\Run"), QSettings::NativeFormat)
.value(QStringLiteral(PROJECT_NAME))
.toString();
#else
return std::nullopt;
#endif
}
/*!
* \brief Returns the autostart path that will be configured by invoking setAutostartEnabled(true).
* \remarks
* - Only implemented under Linux/Windows/Mac. Always returns false on other platforms.
* - Does not check whether the startup entry is functional (eg. the specified path is still valid and points to the
* currently running instance of the application).
*/
QString supposedAutostartPath()
{
#if defined(PLATFORM_LINUX) && !defined(Q_OS_ANDROID)
#ifndef SYNCTHINGWIDGETS_AUTOSTART_EXEC_PATH
#define SYNCTHINGWIDGETS_AUTOSTART_EXEC_PATH QCoreApplication::applicationFilePath()
#endif
return qEnvironmentVariable("APPIMAGE", SYNCTHINGWIDGETS_AUTOSTART_EXEC_PATH);
#elif defined(PLATFORM_WINDOWS)
return QCoreApplication::applicationFilePath().replace(QChar('/'), QChar('\\'));
#else
return QCoreApplication::applicationFilePath();
#endif
}
/*!
* \brief Sets the \a path of the application's autostart entry or removes the entry if \a path is empty.
*/
bool setAutostartPath(const QString &path)
{
#if defined(PLATFORM_LINUX) && !defined(Q_OS_ANDROID)
const auto configPath = QStandardPaths::writableLocation(QStandardPaths::ConfigLocation);
if (configPath.isEmpty()) {
return false;
}
if (!path.isEmpty() && !QDir().mkpath(configPath + QStringLiteral("/autostart"))) {
return false;
}
auto desktopFile = QFile(configPath + QStringLiteral("/autostart/" PROJECT_NAME ".desktop"));
if (!path.isEmpty()) {
if (!desktopFile.open(QFile::WriteOnly | QFile::Truncate)) {
return false;
}
desktopFile.write("[Desktop Entry]\n"
"Name=" APP_NAME "\n"
"Exec=\"");
desktopFile.write(path.toUtf8());
desktopFile.write("\" qt-widgets-gui --single-instance\nComment=" APP_DESCRIPTION "\n"
"Icon=" PROJECT_NAME "\n"
"Type=Application\n"
"Terminal=false\n"
"X-GNOME-Autostart-Delay=0\n"
"X-GNOME-Autostart-enabled=true");
return desktopFile.error() == QFile::NoError && desktopFile.flush();
} else {
return !desktopFile.exists() || desktopFile.remove();
}
#elif defined(PLATFORM_WINDOWS)
auto settings = QSettings(QStringLiteral("HKEY_CURRENT_USER\\SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\Run"), QSettings::NativeFormat);
if (!path.isEmpty()) {
settings.setValue(QStringLiteral(PROJECT_NAME), path);
} else {
settings.remove(QStringLiteral(PROJECT_NAME));
}
settings.sync();
return true;
#elif defined(PLATFORM_MAC)
const auto libraryPath = QDir::home().filePath(QStringLiteral("Library"));
if (!path.isEmpty() && !QDir().mkpath(libraryPath + QStringLiteral("/LaunchAgents"))) {
return false;
}
auto launchdPlistFile = QFile(libraryPath + QStringLiteral("/LaunchAgents/" PROJECT_NAME ".plist"));
if (!path.isEmpty()) {
if (!launchdPlistFile.open(QFile::WriteOnly | QFile::Truncate)) {
return false;
}
launchdPlistFile.write("<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n"
"<!DOCTYPE plist PUBLIC \"-//Apple//DTD PLIST 1.0//EN\" \"http://www.apple.com/DTDs/PropertyList-1.0.dtd\">\n"
"<plist version=\"1.0\">\n"
" <dict>\n"
" <key>Label</key>\n"
" <string>" PROJECT_NAME "</string>\n"
" <key>ProgramArguments</key>\n"
" <array>\n"
" <string>");
launchdPlistFile.write(path.toUtf8());
launchdPlistFile.write("</string>\n"
" </array>\n"
" <key>KeepAlive</key>\n"
" <true/>\n"
" </dict>\n"
"</plist>\n");
return launchdPlistFile.error() == QFile::NoError && launchdPlistFile.flush();
} else {
return !launchdPlistFile.exists() || launchdPlistFile.remove();
}
#endif
}
/*!
* \brief Returns whether the application is launched on startup.
* \remarks
* - Only implemented under Linux/Windows/Mac. Always returns false on other platforms.
* - Does not check whether the startup entry is functional (e.g. whether the specified path is still valid and points to the
* currently running instance of the application).
*/
bool isAutostartEnabled()
{
#if defined(PLATFORM_LINUX) && !defined(Q_OS_ANDROID)
auto desktopFile = QFile(QStandardPaths::locate(QStandardPaths::ConfigLocation, QStringLiteral("autostart/" PROJECT_NAME ".desktop")));
// check whether the file can be opened and whether it is enabled but prevent reading large files
if (desktopFile.open(QFile::ReadOnly) && (desktopFile.size() > (5 * 1024) || !desktopFile.readAll().contains("Hidden=true"))) {
return true;
}
return false;
#elif defined(PLATFORM_WINDOWS)
return QSettings(QStringLiteral("HKEY_CURRENT_USER\\SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\Run"), QSettings::NativeFormat)
.contains(QStringLiteral(PROJECT_NAME));
#elif defined(PLATFORM_MAC)
return QFileInfo(QDir::home(), QStringLiteral("Library/LaunchAgents/" PROJECT_NAME ".plist")).isReadable();
#else
return false;
#endif
}
/*!
* \brief Sets whether the application is launched on startup.
* \remarks
* - Only implemented under Linux/Windows/Mac. Does nothing on other platforms.
* - If a startup entry already exists and \a enabled is true, this function will not touch the existing entry - even if it points
* to another application. Delete the existing entry first if it is no longer wanted or set \a force to true.
* - Note that is might not be possible to determine the currently configured path. If the path cannot be determined an
* existing autostart entry will always be overridden (despite \a force being false).
* - If no startup entry could be detected via isAutostartEnabled() and \a enabled is false this function doesn't touch anything.
*/
bool setAutostartEnabled(bool enabled, bool force)
{
const auto configuredPath = configuredAutostartPath();
if (!(configuredPath.has_value() ? !configuredPath.value().isEmpty() : isAutostartEnabled()) && !enabled) {
return true;
}
const auto supposedPath = supposedAutostartPath();
if (!force && enabled && configuredPath.has_value() && !configuredPath.value().isEmpty() && configuredPath.value() != supposedPath) {
return true; // don't touch existing entry
}
return setAutostartPath(enabled ? supposedPath : QString());
}
bool AutostartOptionPage::apply()
{
if (m_unsupported) {
return true; // don't treat this as an error
}
if (!setAutostartEnabled(ui()->autostartCheckBox->isChecked())) {
errors() << QCoreApplication::translate("QtGui::AutostartOptionPage", "unable to modify startup entry");
return false;
}
return true;
}
void AutostartOptionPage::reset()
{
if (!hasBeenShown() || m_unsupported) {
return;
}
const auto configuredPath = configuredAutostartPath();
if (!configuredPath.has_value()) { // we can't determine the currently configured path
ui()->pathWidget->setVisible(false);
ui()->autostartCheckBox->setEnabled(true);
ui()->autostartCheckBox->setChecked(isAutostartEnabled());
return;
}
const auto autostartEnabled = !configuredPath.value().isEmpty();
ui()->autostartCheckBox->setChecked(autostartEnabled);
if (!autostartEnabled) {
ui()->pathWidget->setVisible(false);
ui()->autostartCheckBox->setEnabled(true);
return;
}
const auto supposedPath = supposedAutostartPath();
const auto pathMismatch = configuredPath != supposedPath;
ui()->pathWidget->setVisible(pathMismatch);
ui()->autostartCheckBox->setEnabled(!pathMismatch);
if (pathMismatch) {
ui()->pathWarningLabel->setText(QCoreApplication::translate("QtGui::AutostartOptionPage",
"There is already an autostart entry for \"%1\". "
"It will not be overridden when applying changes unless you delete it first.")
.arg(configuredPath.value()));
}
}
// LauncherOptionPage
LauncherOptionPage::LauncherOptionPage(QWidget *parentWidget)
: QObject(parentWidget)
, LauncherOptionPageBase(parentWidget)
, m_process(nullptr)
, m_launcher(SyncthingLauncher::mainInstance())
, m_kill(false)
{
}
LauncherOptionPage::LauncherOptionPage(const QString &tool, const QString &toolName, const QString &windowTitle, QWidget *parentWidget)
: QObject(parentWidget)
, LauncherOptionPageBase(parentWidget)
, m_process(&Launcher::toolProcess(tool))
, m_launcher(nullptr)
, m_restoreArgsAction(nullptr)
, m_kill(false)
, m_tool(tool)
, m_toolName(toolName)
, m_windowTitle(windowTitle)
{
}
LauncherOptionPage::~LauncherOptionPage()
{
}
QWidget *LauncherOptionPage::setupWidget()
{
auto *const widget = LauncherOptionPageBase::setupWidget();
// adjust labels to use name of additional tool instead of "Syncthing"
const auto isSyncthing = m_tool.isEmpty();
if (!isSyncthing) {
widget->setWindowTitle(m_windowTitle.isEmpty() ? tr("%1-launcher").arg(m_tool) : m_windowTitle);
ui()->enabledCheckBox->setText(tr("Launch %1 when starting the tray icon").arg(m_toolName.isEmpty() ? m_tool : m_toolName));
auto toolNameStartingSentence = m_toolName.isEmpty() ? m_tool : m_toolName;
toolNameStartingSentence[0] = toolNameStartingSentence[0].toUpper();
ui()->syncthingPathLabel->setText(tr("%1 executable").arg(toolNameStartingSentence));
ui()->logLabel->setText(tr("%1 log (interleaved stdout/stderr)").arg(toolNameStartingSentence));
// hide "consider for reconnect" and "show start/stop button on tray" checkboxes for tools
ui()->considerForReconnectCheckBox->setVisible(false);
ui()->showButtonCheckBox->setVisible(false);
}
// hide libsyncthing-controls by default (as the checkbox is unchecked by default)
for (auto *const lstWidget : std::initializer_list<QWidget *>{ ui()->configDirLabel, ui()->configDirPathSelection, ui()->dataDirLabel,
ui()->dataDirPathSelection, ui()->logLevelLabel, ui()->logLevelComboBox }) {
lstWidget->setVisible(false);
}
// add "restore to defaults" action for Syncthing arguments
if (isSyncthing) {
m_restoreArgsAction = new QAction(ui()->argumentsLineEdit);
m_restoreArgsAction->setText(tr("Restore default"));
m_restoreArgsAction->setIcon(
QIcon::fromTheme(QStringLiteral("edit-undo"), QIcon(QStringLiteral(":/icons/hicolor/scalable/actions/edit-paste.svg"))));
connect(m_restoreArgsAction, &QAction::triggered, this, &LauncherOptionPage::restoreDefaultArguments);
ui()->argumentsLineEdit->addCustomAction(m_restoreArgsAction);
m_syncthingDownloadAction = new QAction(ui()->syncthingPathSelection);
m_syncthingDownloadAction->setText(tr("Show Syncthing releases/downloads"));
m_syncthingDownloadAction->setIcon(
QIcon::fromTheme(QStringLiteral("download"), QIcon(QStringLiteral(":/icons/hicolor/scalable/actions/download.svg"))));
connect(m_syncthingDownloadAction, &QAction::triggered,
[] { QDesktopServices::openUrl(QUrl(QStringLiteral("https://github.com/syncthing/syncthing/releases"))); });
ui()->syncthingPathSelection->lineEdit()->addCustomAction(m_syncthingDownloadAction);
ui()->configDirPathSelection->provideCustomFileMode(QFileDialog::Directory);
ui()->dataDirPathSelection->provideCustomFileMode(QFileDialog::Directory);
}
// setup other widgets
ui()->syncthingPathSelection->provideCustomFileMode(QFileDialog::ExistingFile);
ui()->logTextEdit->setFont(QFontDatabase::systemFont(QFontDatabase::FixedFont));
const auto running = isRunning();
ui()->launchNowPushButton->setHidden(running);
ui()->stopPushButton->setHidden(!running);
ui()->useBuiltInVersionCheckBox->setVisible(isSyncthing && SyncthingLauncher::isLibSyncthingAvailable());
if (isSyncthing) {
ui()->useBuiltInVersionCheckBox->setToolTip(SyncthingLauncher::libSyncthingVersionInfo());
}
// connect signals & slots
if (m_process) {
connect(m_process, &SyncthingProcess::readyRead, this, &LauncherOptionPage::handleSyncthingReadyRead, Qt::QueuedConnection);
connect(m_process, static_cast<void (SyncthingProcess::*)(int exitCode, QProcess::ExitStatus exitStatus)>(&SyncthingProcess::finished), this,
&LauncherOptionPage::handleSyncthingExited, Qt::QueuedConnection);
connect(m_process, &SyncthingProcess::errorOccurred, this, &LauncherOptionPage::handleSyncthingError, Qt::QueuedConnection);
} else if (m_launcher) {
connect(m_launcher, &SyncthingLauncher::runningChanged, this, &LauncherOptionPage::handleSyncthingLaunched);
connect(m_launcher, &SyncthingLauncher::outputAvailable, this, &LauncherOptionPage::handleSyncthingOutputAvailable, Qt::QueuedConnection);
connect(m_launcher, &SyncthingLauncher::exited, this, &LauncherOptionPage::handleSyncthingExited, Qt::QueuedConnection);
connect(m_launcher, &SyncthingLauncher::errorOccurred, this, &LauncherOptionPage::handleSyncthingError, Qt::QueuedConnection);
#ifdef SYNCTHINGWIDGETS_USE_LIBSYNCTHING
connect(ui()->logLevelComboBox, static_cast<void (QComboBox::*)(int)>(&QComboBox::currentIndexChanged), this,
&LauncherOptionPage::updateLibSyncthingLogLevel);
#endif
m_launcher->setEmittingOutput(true);
}
connect(ui()->launchNowPushButton, &QPushButton::clicked, this, &LauncherOptionPage::launch);
connect(ui()->stopPushButton, &QPushButton::clicked, this, &LauncherOptionPage::stop);
return widget;
}
bool LauncherOptionPage::apply()
{
auto &settings = values().launcher;
if (m_tool.isEmpty()) {
settings.autostartEnabled = ui()->enabledCheckBox->isChecked();
settings.useLibSyncthing = ui()->useBuiltInVersionCheckBox->isChecked();
#ifdef SYNCTHINGWIDGETS_USE_LIBSYNCTHING
settings.libSyncthing.configDir = ui()->configDirPathSelection->lineEdit()->text();
settings.libSyncthing.dataDir = ui()->dataDirPathSelection->lineEdit()->text();
settings.libSyncthing.logLevel = static_cast<LibSyncthing::LogLevel>(ui()->logLevelComboBox->currentIndex());
#endif
settings.syncthingPath = ui()->syncthingPathSelection->lineEdit()->text();
settings.syncthingArgs = ui()->argumentsLineEdit->text();
settings.considerForReconnect = ui()->considerForReconnectCheckBox->isChecked();
settings.showButton = ui()->showButtonCheckBox->isChecked();
} else {
ToolParameter &params = settings.tools[m_tool];
params.autostart = ui()->enabledCheckBox->isChecked();
params.path = ui()->syncthingPathSelection->lineEdit()->text();
params.args = ui()->argumentsLineEdit->text();
}
return true;
}
void LauncherOptionPage::reset()
{
const auto &settings = values().launcher;
if (m_tool.isEmpty()) {
ui()->enabledCheckBox->setChecked(settings.autostartEnabled);
ui()->useBuiltInVersionCheckBox->setChecked(settings.useLibSyncthing);
ui()->useBuiltInVersionCheckBox->setVisible(settings.useLibSyncthing || SyncthingLauncher::isLibSyncthingAvailable());
#ifdef SYNCTHINGWIDGETS_USE_LIBSYNCTHING
ui()->configDirPathSelection->lineEdit()->setText(settings.libSyncthing.configDir);
ui()->dataDirPathSelection->lineEdit()->setText(settings.libSyncthing.dataDir);
ui()->logLevelComboBox->setCurrentIndex(static_cast<int>(settings.libSyncthing.logLevel));
#endif
ui()->syncthingPathSelection->lineEdit()->setText(settings.syncthingPath);
ui()->argumentsLineEdit->setText(settings.syncthingArgs);
ui()->considerForReconnectCheckBox->setChecked(settings.considerForReconnect);
ui()->showButtonCheckBox->setChecked(settings.showButton);
} else {
const ToolParameter params = settings.tools.value(m_tool);
ui()->useBuiltInVersionCheckBox->setChecked(false);
ui()->useBuiltInVersionCheckBox->setVisible(false);
ui()->enabledCheckBox->setChecked(params.autostart);
ui()->syncthingPathSelection->lineEdit()->setText(params.path);
ui()->argumentsLineEdit->setText(params.args);
}
}
void LauncherOptionPage::handleSyncthingLaunched(bool running)
{
if (!running) {
return; // Syncthing being stopped is handled elsewhere
}
ui()->launchNowPushButton->hide();
ui()->stopPushButton->show();
ui()->stopPushButton->setText(tr("Stop launched instance"));
m_kill = false;
}
void LauncherOptionPage::handleSyncthingReadyRead()
{
handleSyncthingOutputAvailable(m_process->readAll());
}
void LauncherOptionPage::handleSyncthingOutputAvailable(const QByteArray &output)
{
if (!hasBeenShown()) {
return;
}
QTextCursor cursor(ui()->logTextEdit->textCursor());
cursor.movePosition(QTextCursor::End);
cursor.insertText(QString::fromUtf8(output));
if (ui()->ensureCursorVisibleCheckBox->isChecked()) {
ui()->logTextEdit->moveCursor(QTextCursor::End);
ui()->logTextEdit->ensureCursorVisible();
}
}
void LauncherOptionPage::handleSyncthingExited(int exitCode, QProcess::ExitStatus exitStatus)
{
if (!hasBeenShown()) {
return;
}
QTextCursor cursor(ui()->logTextEdit->textCursor());
cursor.movePosition(QTextCursor::End);
cursor.insertBlock();
switch (exitStatus) {
case QProcess::NormalExit:
cursor.insertText(tr("%1 exited with exit code %2").arg(m_tool.isEmpty() ? QStringLiteral("Syncthing") : m_tool, QString::number(exitCode)));
break;
case QProcess::CrashExit:
cursor.insertText(tr("%1 crashed with exit code %2").arg(m_tool.isEmpty() ? QStringLiteral("Syncthing") : m_tool, QString::number(exitCode)));
break;
}
cursor.insertBlock();
if (ui()->ensureCursorVisibleCheckBox->isChecked()) {
ui()->logTextEdit->moveCursor(QTextCursor::End);
ui()->logTextEdit->ensureCursorVisible();
}
ui()->stopPushButton->hide();
ui()->launchNowPushButton->show();
}
void LauncherOptionPage::handleSyncthingError(QProcess::ProcessError error)
{
if (!hasBeenShown()) {
return;
}
QTextCursor cursor(ui()->logTextEdit->textCursor());
cursor.movePosition(QTextCursor::End);
cursor.insertBlock();
auto errorString = QString();
if (m_launcher) {
errorString = m_launcher->errorString();
} else if (m_process) {
errorString = m_process->errorString();
}
if (errorString.isEmpty()) {
switch (error) {
case QProcess::FailedToStart:
errorString = tr("failed to start (e.g. executable does not exist or not permission error)");
break;
case QProcess::Crashed:
errorString = tr("process crashed");
break;
case QProcess::Timedout:
errorString = tr("timeout error");
break;
case QProcess::ReadError:
errorString = tr("read error");
break;
case QProcess::WriteError:
errorString = tr("write error");
break;
default:
errorString = tr("unknown process error");
}
}
cursor.insertText(tr("An error occurred when running %1: %2").arg(m_tool.isEmpty() ? QStringLiteral("Syncthing") : m_tool, errorString));
cursor.insertBlock();
if ((m_launcher && !m_launcher->isRunning()) || (m_process && !m_process->isRunning())) {
ui()->stopPushButton->hide();
ui()->launchNowPushButton->show();
}
}
bool LauncherOptionPage::isRunning() const
{
return (m_process && m_process->isRunning()) || (m_launcher && m_launcher->isRunning());
}
void LauncherOptionPage::launch()
{
if (!hasBeenShown()) {
return;
}
apply();
if (isRunning()) {
return;
}
const auto &launcherSettings(values().launcher);
if (m_tool.isEmpty()) {
m_launcher->launch(launcherSettings);
return;
}
const auto toolParams(launcherSettings.tools.value(m_tool));
m_process->startSyncthing(toolParams.path, SyncthingProcess::splitArguments(toolParams.args));
handleSyncthingLaunched(true);
}
#ifdef SYNCTHINGWIDGETS_USE_LIBSYNCTHING
void LauncherOptionPage::updateLibSyncthingLogLevel()
{
m_launcher->setLibSyncthingLogLevel(static_cast<LibSyncthing::LogLevel>(ui()->logLevelComboBox->currentIndex()));
}
#endif
void LauncherOptionPage::stop()
{
if (!hasBeenShown()) {
return;
}
if (m_kill) {
if (m_process) {
m_process->killSyncthing();
}
if (m_launcher) {
m_launcher->kill();
}
} else {
ui()->stopPushButton->setText(tr("Kill launched instance"));
m_kill = true;
if (m_process) {
m_process->stopSyncthing();
}
if (m_launcher) {
m_launcher->terminate(Launcher::connectionForLauncher(m_launcher));
}
}
}
void LauncherOptionPage::restoreDefaultArguments()
{
static const ::Settings::Launcher defaults;
ui()->argumentsLineEdit->setText(defaults.syncthingArgs);
}
// SystemdOptionPage
#ifdef LIB_SYNCTHING_CONNECTOR_SUPPORT_SYSTEMD
SystemdOptionPage::SystemdOptionPage(QWidget *parentWidget)
: SystemdOptionPageBase(parentWidget)
, m_service(SyncthingService::mainInstance())
{
}
SystemdOptionPage::~SystemdOptionPage()
{
QObject::disconnect(m_unitChangedConn);
QObject::disconnect(m_descChangedConn);
QObject::disconnect(m_statusChangedConn);
QObject::disconnect(m_enabledChangedConn);
}
QWidget *SystemdOptionPage::setupWidget()
{
auto *const widget = SystemdOptionPageBase::setupWidget();
auto *const refreshAction = new QAction(QCoreApplication::translate("QtGui::SystemdOptionPage", "Reload all unit files"), widget);
refreshAction->setIcon(
QIcon::fromTheme(QStringLiteral("view-refresh"), QIcon(QStringLiteral(":/icons/hicolor/scalable/actions/view-refresh.svg"))));
ui()->syncthingUnitLineEdit->addCustomAction(refreshAction);
if (!m_service) {
return widget;
}
QObject::connect(refreshAction, &QAction::triggered, m_service, &SyncthingService::reloadAllUnitFiles);
QObject::connect(ui()->syncthingUnitLineEdit, &QLineEdit::textChanged, m_service, &SyncthingService::setUnitName);
QObject::connect(ui()->startPushButton, &QPushButton::clicked, m_service, &SyncthingService::start);
QObject::connect(ui()->stopPushButton, &QPushButton::clicked, m_service, &SyncthingService::stop);
QObject::connect(ui()->enablePushButton, &QPushButton::clicked, m_service, &SyncthingService::enable);
QObject::connect(ui()->disablePushButton, &QPushButton::clicked, m_service, &SyncthingService::disable);
m_unitChangedConn
= QObject::connect(ui()->systemUnitCheckBox, &QCheckBox::clicked, m_service, bind(&SystemdOptionPage::handleSystemUnitChanged, this));
m_descChangedConn
= QObject::connect(m_service, &SyncthingService::descriptionChanged, bind(&SystemdOptionPage::handleDescriptionChanged, this, _1));
m_statusChangedConn
= QObject::connect(m_service, &SyncthingService::stateChanged, bind(&SystemdOptionPage::handleStatusChanged, this, _1, _2, _3));
m_enabledChangedConn
= QObject::connect(m_service, &SyncthingService::unitFileStateChanged, bind(&SystemdOptionPage::handleEnabledChanged, this, _1));
if (const auto *optionPageWidget = qobject_cast<OptionPageWidget *>(widget)) {
QObject::connect(optionPageWidget, &OptionPageWidget::paletteChanged, std::bind(&SystemdOptionPage::updateColors, this));
}
return widget;
}
bool SystemdOptionPage::apply()
{
auto &settings = values();
auto &systemdSettings = settings.systemd;
auto &launcherSettings = settings.launcher;
systemdSettings.syncthingUnit = ui()->syncthingUnitLineEdit->text();
systemdSettings.systemUnit = ui()->systemUnitCheckBox->isChecked();
systemdSettings.showButton = ui()->showButtonCheckBox->isChecked();
systemdSettings.considerForReconnect = ui()->considerForReconnectCheckBox->isChecked();
auto result = true;
if (systemdSettings.showButton && launcherSettings.showButton) {
errors().append(QCoreApplication::translate("QtGui::SystemdOptionPage",
"It is not possible to show the start/stop button for the systemd service and the internal launcher at the same time. The systemd "
"service precedes."));
result = false;
}
if (systemdSettings.considerForReconnect && launcherSettings.considerForReconnect) {
errors().append(QCoreApplication::translate("QtGui::SystemdOptionPage",
"It is not possible to consider the systemd service and the internal launcher for reconnects at the same time. The systemd service "
"precedes."));
result = false;
}
return result;
}
void SystemdOptionPage::reset()
{
const auto &settings = values().systemd;
ui()->syncthingUnitLineEdit->setText(settings.syncthingUnit);
ui()->systemUnitCheckBox->setChecked(settings.systemUnit);
ui()->showButtonCheckBox->setChecked(settings.showButton);
ui()->considerForReconnectCheckBox->setChecked(settings.considerForReconnect);
if (!m_service) {
return;
}
handleDescriptionChanged(m_service->description());
handleStatusChanged(m_service->activeState(), m_service->subState(), m_service->activeSince());
handleEnabledChanged(m_service->unitFileState());
}
void SystemdOptionPage::handleSystemUnitChanged()
{
m_service->setScope(ui()->systemUnitCheckBox->isChecked() ? SystemdScope::System : SystemdScope::User);
}
void SystemdOptionPage::handleDescriptionChanged(const QString &description)
{
ui()->descriptionValueLabel->setText(description.isEmpty()
? QCoreApplication::translate("QtGui::SystemdOptionPage", "specified unit is either inactive or doesn't exist")
: description);
}
static void setIndicatorColor(QWidget *indicator, const QColor &color)
{
indicator->setStyleSheet(QStringLiteral("border-radius:8px;background-color:") + color.name());
}
void SystemdOptionPage::handleStatusChanged(const QString &activeState, const QString &subState, DateTime activeSince)
{
m_status.clear();
if (!activeState.isEmpty()) {
m_status << activeState;
}
if (!subState.isEmpty()) {
m_status << subState;
}
const bool isRunning = updateRunningColor();
QString timeStamp;
if (isRunning && !activeSince.isNull()) {
timeStamp = QLatin1Char('\n') % QCoreApplication::translate("QtGui::SystemdOptionPage", "since ")
% QString::fromUtf8(activeSince.toString(DateTimeOutputFormat::DateAndTime).data());
}
ui()->statusValueLabel->setText(
m_status.isEmpty() ? QCoreApplication::translate("QtGui::SystemdOptionPage", "unknown") : m_status.join(QStringLiteral(" - ")) + timeStamp);
ui()->startPushButton->setVisible(!isRunning);
ui()->stopPushButton->setVisible(!m_status.isEmpty() && isRunning);
}
void SystemdOptionPage::handleEnabledChanged(const QString &unitFileState)
{
const auto isEnabled = updateEnabledColor();
ui()->unitFileStateValueLabel->setText(
unitFileState.isEmpty() ? QCoreApplication::translate("QtGui::SystemdOptionPage", "unknown") : unitFileState);
ui()->enablePushButton->setVisible(!isEnabled);
ui()->disablePushButton->setVisible(!unitFileState.isEmpty() && isEnabled);
}
bool SystemdOptionPage::updateRunningColor()
{
const bool isRunning = m_service && m_service->isRunning();
const auto brightColors = isPaletteDark(widget()->palette());
setIndicatorColor(ui()->statusIndicator,
m_status.isEmpty() ? Colors::gray(brightColors) : (isRunning ? Colors::green(brightColors) : Colors::red(brightColors)));
return isRunning;
}
bool SystemdOptionPage::updateEnabledColor()
{
const auto isEnabled = m_service && m_service->isEnabled();
const auto brightColors = isPaletteDark(widget()->palette());
setIndicatorColor(ui()->enabledIndicator, isEnabled ? Colors::green(brightColors) : Colors::gray(brightColors));
return isEnabled;
}
void SystemdOptionPage::updateColors()
{
updateRunningColor();
updateEnabledColor();
}
#endif
// GeneralWebViewOptionPage
GeneralWebViewOptionPage::GeneralWebViewOptionPage(QWidget *parentWidget)
: GeneralWebViewOptionPageBase(parentWidget)
{
}
GeneralWebViewOptionPage::~GeneralWebViewOptionPage()
{
}
QWidget *GeneralWebViewOptionPage::setupWidget()
{
auto *const widget = GeneralWebViewOptionPageBase::setupWidget();
auto *const cfgToolButton = ui()->appModeCfgToolButton;
#ifdef SYNCTHINGWIDGETS_NO_WEBVIEW
ui()->builtinRadioButton->setEnabled(false);
#endif
const auto minHeight = cfgToolButton->height();
ui()->builtinRadioButton->setMinimumHeight(minHeight);
ui()->browserRadioButton->setMinimumHeight(minHeight);
ui()->appModeRadioButton->setMinimumHeight(minHeight);
QObject::connect(cfgToolButton, &QToolButton::clicked, cfgToolButton, [this] { showCustomCommandPrompt(); });
return widget;
}
bool GeneralWebViewOptionPage::apply()
{
auto &webView = values().webView;
if (ui()->builtinRadioButton->isChecked()) {
webView.mode = ::Settings::WebView::Mode::Builtin;
} else if (ui()->browserRadioButton->isChecked()) {
webView.mode = ::Settings::WebView::Mode::Browser;
} else if (ui()->appModeRadioButton->isChecked()) {
webView.mode = ::Settings::WebView::Mode::Command;
}
webView.customCommand = m_customCommand;
return true;
}
void GeneralWebViewOptionPage::reset()
{
const auto &webView = values().webView;
switch (webView.mode) {
case ::Settings::WebView::Mode::Builtin:
#ifndef SYNCTHINGWIDGETS_NO_WEBVIEW
ui()->builtinRadioButton->setChecked(true);
break;
#endif
case ::Settings::WebView::Mode::Browser:
ui()->browserRadioButton->setChecked(true);
break;
case ::Settings::WebView::Mode::Command:
ui()->appModeRadioButton->setChecked(true);
break;
}
m_customCommand = webView.customCommand;
}
void GeneralWebViewOptionPage::showCustomCommandPrompt()
{
auto dlg = QInputDialog();
dlg.setInputMode(QInputDialog::TextInput);
dlg.setWindowTitle(
QCoreApplication::translate("QtGui::GeneralWebViewOptionPage", "Custom command to launch Syncthing's UI - ") + QStringLiteral(APP_NAME));
dlg.setLabelText(QCoreApplication::translate("QtGui::GeneralWebViewOptionPage",
"<p>Enter a custom command to launch Syncthing's UI. The expression <code>%SYNCTHING_URL%</code> will be replaced with the "
"Syncthing-URL.</p><p>Leave the command empty to use the auto-detection.</p>"));
dlg.setTextValue(m_customCommand);
if (dlg.exec() == QDialog::Accepted) {
m_customCommand = dlg.textValue();
}
}
// BuiltinWebViewOptionPage
BuiltinWebViewOptionPage::BuiltinWebViewOptionPage(QWidget *parentWidget)
: BuiltinWebViewOptionPageBase(parentWidget)
{
}
BuiltinWebViewOptionPage::~BuiltinWebViewOptionPage()
{
}
#ifdef SYNCTHINGWIDGETS_NO_WEBVIEW
QWidget *BuiltinWebViewOptionPage::setupWidget()
{
auto *label = new QLabel;
label->setWindowTitle(QCoreApplication::translate("QtGui::BuiltinWebViewOptionPage", "Built-in web view"));
label->setAlignment(Qt::AlignCenter);
label->setText(QCoreApplication::translate("QtGui::BuiltinWebViewOptionPage",
"Syncthing Tray has not been built with vieb view support utilizing either Qt WebKit "
"or Qt WebEngine."));
return label;
}
#endif
bool BuiltinWebViewOptionPage::apply()
{
#ifndef SYNCTHINGWIDGETS_NO_WEBVIEW
auto &webView = values().webView;
webView.zoomFactor = ui()->zoomDoubleSpinBox->value();
webView.keepRunning = ui()->keepRunningCheckBox->isChecked();
#endif
return true;
}
void BuiltinWebViewOptionPage::reset()
{
#ifndef SYNCTHINGWIDGETS_NO_WEBVIEW
const auto &webView = values().webView;
ui()->zoomDoubleSpinBox->setValue(webView.zoomFactor);
ui()->keepRunningCheckBox->setChecked(webView.keepRunning);
#endif
}
SettingsDialog::SettingsDialog(const QList<OptionCategory *> &categories, QWidget *parent)
: QtUtilities::SettingsDialog(parent)
{
categoryModel()->setCategories(categories);
init();
}
SettingsDialog::SettingsDialog(QWidget *parent)
: QtUtilities::SettingsDialog(parent)
{
init();
}
SettingsDialog::SettingsDialog(Data::SyncthingConnection *connection, QWidget *parent)
: QtUtilities::SettingsDialog(parent)
{
setWindowFlags(windowFlags() | Qt::WindowMaximizeButtonHint);
// setup categories
QList<OptionCategory *> categories;
OptionCategory *category;
category = new OptionCategory(this);
translateCategory(category, [] { return tr("Tray"); });
category->assignPages({ m_connectionsOptionPage = new ConnectionOptionPage(connection), new NotificationsOptionPage,
m_appearanceOptionPage = new AppearanceOptionPage, new IconsOptionPage(IconsOptionPage::Context::UI),
new IconsOptionPage(IconsOptionPage::Context::System) });
category->setIcon(QIcon(QStringLiteral(":/icons/hicolor/scalable/app/syncthingtray.svg")));
categories << category;
category = new OptionCategory(this);
translateCategory(category, [] { return tr("Web view"); });
category->assignPages({ new GeneralWebViewOptionPage, new BuiltinWebViewOptionPage });
category->setIcon(
QIcon::fromTheme(QStringLiteral("internet-web-browser"), QIcon(QStringLiteral(":/icons/hicolor/scalable/apps/internet-web-browser.svg"))));
categories << category;
category = new OptionCategory(this);
translateCategory(category, [] { return tr("Startup"); });
category->assignPages({ new AutostartOptionPage, new LauncherOptionPage,
new LauncherOptionPage(QStringLiteral("Process"), tr("additional tool"), tr("Extra launcher"))
#ifdef LIB_SYNCTHING_CONNECTOR_SUPPORT_SYSTEMD
,
new SystemdOptionPage
#endif
});
category->setIcon(QIcon::fromTheme(QStringLiteral("system-run"), QIcon(QStringLiteral(":/icons/hicolor/scalable/apps/system-run.svg"))));
m_launcherSettingsCategory = static_cast<int>(categories.size());
m_launcherSettingsPageIndex = 1;
categories << category;
categories << values().qt.category();
categoryModel()->setCategories(categories);
init();
}
SettingsDialog::~SettingsDialog()
{
}
void SettingsDialog::init()
{
resize(1100, 750);
setWindowTitle(tr("Settings") + QStringLiteral(" - " APP_NAME));
setWindowIcon(
QIcon::fromTheme(QStringLiteral("preferences-other"), QIcon(QStringLiteral(":/icons/hicolor/scalable/apps/preferences-other.svg"))));
// add button for starting wizard
auto *startWizardButton = new QPushButton(this);
startWizardButton->setToolTip(tr("Start wizard"));
startWizardButton->setIcon(
QIcon::fromTheme(QStringLiteral("quickwizard"), QIcon(QStringLiteral(":/icons/hicolor/scalable/actions/tools-wizard.svg"))));
startWizardButton->setFlat(true);
startWizardButton->setSizePolicy(QSizePolicy::Minimum, QSizePolicy::Minimum);
connect(startWizardButton, &QPushButton::clicked, this, &SettingsDialog::wizardRequested);
addHeadingWidget(startWizardButton);
// some settings could be applied without restarting the application, good idea?
//connect(this, &Dialogs::SettingsDialog::applied, bind(&Dialogs::QtSettings::apply, &Settings::qtSettings()));
}
void SettingsDialog::hideConnectionStatus()
{
if (m_connectionsOptionPage) {
m_connectionsOptionPage->hideConnectionStatus();
}
}
void SettingsDialog::resetPositioningSettings()
{
if (m_appearanceOptionPage && m_appearanceOptionPage->hasBeenShown()) {
m_appearanceOptionPage->resetPositioningSettings();
}
}
void SettingsDialog::selectLauncherSettings()
{
if (m_launcherSettingsCategory >= 0 && m_launcherSettingsPageIndex >= 0) {
selectPage(m_launcherSettingsCategory, m_launcherSettingsPageIndex);
}
}
} // namespace QtGui
INSTANTIATE_UI_FILE_BASED_OPTION_PAGE_NS(QtGui, ConnectionOptionPage)
INSTANTIATE_UI_FILE_BASED_OPTION_PAGE_NS(QtGui, NotificationsOptionPage)
INSTANTIATE_UI_FILE_BASED_OPTION_PAGE_NS(QtGui, AppearanceOptionPage)
INSTANTIATE_UI_FILE_BASED_OPTION_PAGE_NS(QtGui, IconsOptionPage)
INSTANTIATE_UI_FILE_BASED_OPTION_PAGE_NS(QtGui, AutostartOptionPage)
INSTANTIATE_UI_FILE_BASED_OPTION_PAGE_NS(QtGui, LauncherOptionPage)
#ifdef LIB_SYNCTHING_CONNECTOR_SUPPORT_SYSTEMD
INSTANTIATE_UI_FILE_BASED_OPTION_PAGE_NS(QtGui, SystemdOptionPage)
#endif
INSTANTIATE_UI_FILE_BASED_OPTION_PAGE_NS(QtGui, GeneralWebViewOptionPage)
#ifndef SYNCTHINGWIDGETS_NO_WEBVIEW
INSTANTIATE_UI_FILE_BASED_OPTION_PAGE_NS(QtGui, BuiltinWebViewOptionPage)
#endif