Improve first launch message in preparation for adding a setup wizard

This commit is contained in:
Martchus 2022-08-06 21:08:55 +02:00
parent 04a4fe89ec
commit 394af9bce7
8 changed files with 194 additions and 21 deletions

View File

@ -67,6 +67,7 @@ set(REQUIRED_ICONS
go-down
go-up
help-about
help-contents
internet-web-browser
list-add
list-remove

View File

@ -5,6 +5,7 @@
#include <syncthingwidgets/misc/syncthinglauncher.h>
#include <syncthingwidgets/settings/settings.h>
#include <syncthingwidgets/settings/wizard.h>
#include <syncthingconnector/syncthingprocess.h>
#ifdef LIB_SYNCTHING_CONNECTOR_SUPPORT_SYSTEMD
@ -22,6 +23,7 @@
#include <c++utilities/application/commandlineutils.h>
#include <c++utilities/misc/parseerror.h>
#include <qtutilities/misc/dialogutils.h>
#include <qtutilities/resources/importplugin.h>
#include <qtutilities/resources/qtconfigarguments.h>
#include <qtutilities/resources/resources.h>
@ -60,6 +62,14 @@ void handleSystemdServiceError(const QString &context, const QString &name, cons
}
#endif
static void showWizard(const TrayWidget *trayWidget)
{
auto *const wizard = Wizard::instance();
QtUtilities::centerWidget(wizard);
QObject::connect(wizard, &Wizard::settingsRequested, trayWidget, &TrayWidget::showSettingsDialog);
wizard->show();
}
int initSyncthingTray(bool windowed, bool waitForTray, const Argument &connectionConfigArg)
{
// get settings
@ -105,17 +115,10 @@ int initSyncthingTray(bool windowed, bool waitForTray, const Argument &connectio
widget = &trayIcon->trayMenu().widget();
}
// show "first launch" message box
if (!settings.firstLaunch) {
return 0;
// show wizard on first launch
if (settings.firstLaunch || settings.fakeFirstLaunch) {
showWizard(widget);
}
QMessageBox msgBox;
msgBox.setIcon(QMessageBox::Information);
msgBox.setText(QCoreApplication::translate("main", "You must configure how to connect to Syncthing when using Syncthing Tray the first time."));
msgBox.setInformativeText(QCoreApplication::translate(
"main", "Note that the settings dialog allows importing URL, credentials and API-key from the local Syncthing configuration."));
msgBox.exec();
widget->showSettingsDialog();
return 0;
#else
@ -128,9 +131,9 @@ int initSyncthingTray(bool windowed, bool waitForTray, const Argument &connectio
#endif
}
void trigger(bool tray, bool webUi)
static void trigger(bool tray, bool webUi, bool wizard)
{
if (TrayWidget::instances().empty() || !(tray || webUi)) {
if (TrayWidget::instances().empty() || !(tray || webUi || wizard)) {
return;
}
auto *const trayWidget = TrayWidget::instances().front();
@ -140,6 +143,9 @@ void trigger(bool tray, bool webUi)
if (tray) {
trayWidget->showUsingPositioningSettings();
}
if (wizard) {
showWizard(trayWidget);
}
}
void shutdownSyncthingTray()
@ -159,6 +165,10 @@ int runApplication(int argc, const char *const *argv)
auto windowedArg = ConfigValueArgument("windowed", 'w', "opens the tray menu as a regular window");
auto showWebUiArg = ConfigValueArgument("webui", '\0', "instantly shows the web UI - meant for creating shortcut to web UI");
auto triggerArg = ConfigValueArgument("trigger", '\0', "instantly shows the left-click tray menu - meant for creating a shortcut");
auto showWizardArg = ConfigValueArgument("show-wizard", '\0', "instantly shows the setup wizard");
showWizardArg.setFlags(Argument::Flags::Deprecated, true); // hide as it is WIP
auto assumeFirstLaunchArg = ConfigValueArgument("assume-first-launch", '\0', "assumes first launch");
assumeFirstLaunchArg.setFlags(Argument::Flags::Deprecated, true); // hide as it is debug-only
auto waitForTrayArg = ConfigValueArgument("wait", '\0',
"wait until the system tray becomes available instead of showing an error message if the system tray is not available on start-up");
auto connectionArg = ConfigValueArgument("connection", '\0', "specifies one or more connection configurations to be used", { "config name" });
@ -168,8 +178,8 @@ int runApplication(int argc, const char *const *argv)
auto singleInstance = Argument("single-instance", '\0', "does nothing if a tray icon is already shown");
auto newInstanceArg = Argument("new-instance", '\0', "disable the usual single-process behavior");
auto &widgetsGuiArg = qtConfigArgs.qtWidgetsGuiArg();
widgetsGuiArg.addSubArguments(
{ &windowedArg, &showWebUiArg, &triggerArg, &waitForTrayArg, &connectionArg, &configPathArg, &singleInstance, &newInstanceArg });
widgetsGuiArg.addSubArguments({ &windowedArg, &showWebUiArg, &triggerArg, &waitForTrayArg, &connectionArg, &configPathArg, &singleInstance,
&newInstanceArg, &showWizardArg, &assumeFirstLaunchArg });
#ifdef SYNCTHINGTRAY_USE_LIBSYNCTHING
auto cliArg = OperationArgument("cli", 'c', "run Syncthing's CLI");
auto cliHelp = ConfigValueArgument("help", 'h', "show help for Syncthing's CLI");
@ -214,6 +224,9 @@ int runApplication(int argc, const char *const *argv)
Settings::restore();
Settings::values().qt.apply();
qtConfigArgs.applySettings(true);
if (assumeFirstLaunchArg.isPresent()) {
Settings::values().fakeFirstLaunch = true;
}
LOAD_QT_TRANSLATIONS;
SyncthingLauncher launcher;
SyncthingLauncher::setMainInstance(&launcher);
@ -232,14 +245,14 @@ int runApplication(int argc, const char *const *argv)
// trigger UI and enter event loop
QObject::connect(&application, &QCoreApplication::aboutToQuit, &shutdownSyncthingTray);
trigger(triggerArg.isPresent(), showWebUiArg.isPresent());
trigger(triggerArg.isPresent(), showWebUiArg.isPresent(), showWizardArg.isPresent());
return application.exec();
}
// trigger actions if --webui or --trigger is present but don't create a new tray icon
const auto firstInstance = TrayWidget::instances().empty();
if (!firstInstance && (showWebUiArg.isPresent() || triggerArg.isPresent())) {
trigger(triggerArg.isPresent(), showWebUiArg.isPresent());
if (!firstInstance && (showWebUiArg.isPresent() || triggerArg.isPresent() || showWizardArg.isPresent())) {
trigger(triggerArg.isPresent(), showWebUiArg.isPresent(), showWizardArg.isPresent());
return 0;
}
@ -251,7 +264,7 @@ int runApplication(int argc, const char *const *argv)
// create new/additional tray icon
const auto res = initSyncthingTray(windowedArg.isPresent(), waitForTrayArg.isPresent(), connectionArg);
if (!res) {
trigger(triggerArg.isPresent(), showWebUiArg.isPresent());
trigger(triggerArg.isPresent(), showWebUiArg.isPresent(), showWizardArg.isPresent());
}
return res;
}

View File

@ -233,7 +233,7 @@ TrayWidget::~TrayWidget()
}
}
void TrayWidget::showSettingsDialog()
SettingsDialog *TrayWidget::settingsDialog()
{
if (!s_dialogParent) {
s_dialogParent = new QWidget();
@ -248,9 +248,14 @@ void TrayWidget::showSettingsDialog()
// by simply saving the settings immediately.
connect(s_settingsDlg, &SettingsDialog::applied, &Settings::save);
}
return s_settingsDlg;
}
void TrayWidget::showSettingsDialog()
{
auto *const dlg = settingsDialog();
// show settings dialog centered or maximized if the relatively big windows would overflow
showDialog(s_settingsDlg, centerWidgetAvoidingOverflow(s_settingsDlg));
showDialog(dlg, centerWidgetAvoidingOverflow(dlg));
}
void TrayWidget::showAboutDialog()

View File

@ -56,6 +56,7 @@ public:
QMenu *connectionsMenu();
static const std::vector<TrayWidget *> &instances();
Data::SyncthingConnectionSettings *selectedConnection();
SettingsDialog *settingsDialog();
public Q_SLOTS:
void showSettingsDialog();

View File

@ -11,6 +11,7 @@ set(META_WEBVIEW_SRC_DIR webview)
set(WIDGETS_HEADER_FILES
settings/settings.h
settings/settingsdialog.h
settings/wizard.h
webview/webpage.h
webview/webviewdialog.h
misc/textviewdialog.h
@ -25,6 +26,7 @@ set(WIDGETS_HEADER_FILES
set(WIDGETS_SRC_FILES
settings/settings.cpp
settings/settingsdialog.cpp
settings/wizard.cpp
webview/webpage.cpp
webview/webviewdialog.cpp
webview/webviewinterceptor.h
@ -71,7 +73,8 @@ set(REQUIRED_ICONS
network-connect
emblem-remove
go-down
go-up)
go-up
help-contents)
# find c++utilities
find_package(${PACKAGE_NAMESPACE_PREFIX}c++utilities${CONFIGURATION_PACKAGE_SUFFIX} 5.11.0 REQUIRED)

View File

@ -150,6 +150,7 @@ struct SYNCTHINGWIDGETS_EXPORT WebView {
struct SYNCTHINGWIDGETS_EXPORT Settings {
bool firstLaunch = false;
bool fakeFirstLaunch = false; // not persistent, for testing purposes only
Connection connection;
NotifyOn notifyOn;
#ifdef QT_UTILITIES_SUPPORT_DBUS_NOTIFICATIONS

111
widgets/settings/wizard.cpp Normal file
View File

@ -0,0 +1,111 @@
#include "./wizard.h"
#include "./settings.h"
// use meta-data of syncthingtray application here
#include "resources/../../tray/resources/config.h"
#include <QCommandLinkButton>
#include <QCoreApplication>
#include <QDesktopServices>
#include <QFrame>
#include <QLabel>
#include <QUrl>
#include <QVBoxLayout>
#include <string_view>
namespace QtGui {
Wizard *Wizard::s_instance = nullptr;
Wizard::Wizard(QWidget *parent, Qt::WindowFlags flags)
: QWizard(parent, flags)
{
setWindowTitle(tr("Setup wizard - ") + QStringLiteral(APP_NAME));
const auto &settings = Settings::values();
if (settings.firstLaunch || settings.fakeFirstLaunch) {
addPage(new WelcomeWizardPage());
}
}
Wizard::~Wizard()
{
if (this == s_instance) {
s_instance = nullptr;
}
}
Wizard *Wizard::instance()
{
if (!s_instance) {
s_instance = new Wizard();
s_instance->setAttribute(Qt::WA_DeleteOnClose, true);
}
return s_instance;
}
WelcomeWizardPage::WelcomeWizardPage(QWidget *parent)
: QWizardPage(parent)
{
setTitle(tr("Welcome to ") + QStringLiteral(APP_NAME));
setSubTitle(tr("It looks like you're launching %1 for the first time.").arg(QStringLiteral(APP_NAME)));
auto *const infoLabel = new QLabel(this);
infoLabel->setText(QCoreApplication::translate("main",
"You must configure how to connect to Syncthing and how to launch Syncthing (if that's wanted) when using Syncthing Tray the first time. A "
"guided/automated setup is still in the works so the manual setup is currently the only option."));
infoLabel->setWordWrap(true);
auto *const showSettingsCommand = new QCommandLinkButton(this);
showSettingsCommand->setText(tr("Configure connection and launcher settings manually"));
showSettingsCommand->setDescription(
tr("Note that the connection settings allow importing URL, credentials and API-key from the local Syncthing configuration.")
.arg(QStringLiteral(APP_NAME)));
showSettingsCommand->setIcon(QIcon::fromTheme(QStringLiteral("preferences-other")));
connect(showSettingsCommand, &QCommandLinkButton::clicked, this, [this] {
if (auto *const wizard = qobject_cast<Wizard *>(this->wizard())) {
emit wizard->settingsRequested();
wizard->close();
}
});
auto *const line = new QFrame(this);
line->setSizePolicy(QSizePolicy(QSizePolicy::MinimumExpanding, QSizePolicy::MinimumExpanding));
line->setFrameShape(QFrame::HLine);
line->setFrameShadow(QFrame::Sunken);
auto *const showDocsCommand = new QCommandLinkButton(this);
showDocsCommand->setText(tr("Show Syncthing's documentation"));
showDocsCommand->setDescription(tr("It contains general information about configuring Syncthing.").arg(QStringLiteral(APP_NAME)));
showDocsCommand->setIcon(QIcon::fromTheme(QStringLiteral("help-contents")));
connect(showDocsCommand, &QCommandLinkButton::clicked, this, [] { QDesktopServices::openUrl(QStringLiteral("https://docs.syncthing.net/")); });
auto *const showReadmeCommand = new QCommandLinkButton(this);
showReadmeCommand->setText(tr("Show %1's README").arg(QStringLiteral(APP_NAME)));
showReadmeCommand->setDescription(tr("It contains documentation about this GUI integration specifically.").arg(QStringLiteral(APP_NAME)));
showReadmeCommand->setIcon(showDocsCommand->icon());
connect(showReadmeCommand, &QCommandLinkButton::clicked, this, [] {
if constexpr (std::string_view(APP_VERSION).find('-') == std::string_view::npos) {
QDesktopServices::openUrl(QStringLiteral(APP_URL "/blob/v" APP_VERSION "/README.md"));
} else {
QDesktopServices::openUrl(QStringLiteral(APP_URL "/blob/master/README.md"));
}
});
auto *const layout = new QVBoxLayout;
layout->addWidget(infoLabel);
layout->addWidget(showSettingsCommand);
layout->addStretch();
layout->addWidget(line);
layout->addWidget(showDocsCommand);
layout->addWidget(showReadmeCommand);
setLayout(layout);
}
bool WelcomeWizardPage::isComplete() const
{
return false;
}
} // namespace QtGui

38
widgets/settings/wizard.h Normal file
View File

@ -0,0 +1,38 @@
#ifndef SETTINGS_WIZARD_H
#define SETTINGS_WIZARD_H
#include "../global.h"
#include <QWizard>
#include <QWizardPage>
namespace QtGui {
class SYNCTHINGWIDGETS_EXPORT Wizard : public QWizard {
Q_OBJECT
public:
explicit Wizard(QWidget *parent = nullptr, Qt::WindowFlags flags = Qt::WindowFlags());
~Wizard() override;
static Wizard *instance();
Q_SIGNALS:
void settingsRequested();
private:
static Wizard *s_instance;
};
class SYNCTHINGWIDGETS_EXPORT WelcomeWizardPage : public QWizardPage {
Q_OBJECT
public:
explicit WelcomeWizardPage(QWidget *parent = nullptr);
bool isComplete() const override;
};
} // namespace QtGui
#endif // SETTINGS_WIZARD_H