X-Git-Url: https://git.quassel-irc.org/?p=quassel.git;a=blobdiff_plain;f=src%2Fqtui%2Fcoreconfigwizard.cpp;h=72a16bb9494e9e0f4f06a2f495c37ffb160e9215;hp=9dfbc8c2a44343bb40a321bcfca07a5ae04fa1dd;hb=HEAD;hpb=b65b9f7615165e8700a44d59b7275a55558dd45b diff --git a/src/qtui/coreconfigwizard.cpp b/src/qtui/coreconfigwizard.cpp index 9dfbc8c2..72a16bb9 100644 --- a/src/qtui/coreconfigwizard.cpp +++ b/src/qtui/coreconfigwizard.cpp @@ -1,5 +1,5 @@ /*************************************************************************** - * Copyright (C) 2005-2015 by the Quassel Project * + * Copyright (C) 2005-2022 by the Quassel Project * * devel@quassel-irc.org * * * * This program is free software; you can redistribute it and/or modify * @@ -18,39 +18,130 @@ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. * ***************************************************************************/ -#include +#include "coreconfigwizard.h" + #include +#include +#include #include -#include #include -#include "coreconfigwizard.h" +#include "client.h" #include "coreconnection.h" +#include "icon.h" +#include "util.h" + +namespace { + +QGroupBox* createDescriptionBox(const QString& description) +{ + auto box = new QGroupBox; + auto layout = new QVBoxLayout(box); + auto label = new QLabel(description, box); + label->setWordWrap(true); + layout->addWidget(label); + layout->setAlignment(label, Qt::AlignTop); + box->setTitle(QCoreApplication::translate("CoreConfigWizard", "Description")); + return box; +} + +template +QGroupBox* createFieldBox(const QString& title, const std::vector& fieldInfos) +{ + // Create a config UI based on the field types sent from the backend + // We make some assumptions here (like integer range and password field names) that may not + // hold true for future authenticator types - but the only way around it for now would be to + // provide specialized config widgets for those (which may be a good idea anyway, e.g. if we + // think about client-side translations...) + + auto* fieldBox = new QGroupBox; + fieldBox->setTitle(title); + auto* formLayout = new QFormLayout; + fieldBox->setLayout(formLayout); + + for (auto&& fieldInfo : fieldInfos) { + QWidget* widget{nullptr}; + switch (std::get<2>(fieldInfo).type()) { + case QVariant::Int: + widget = new QSpinBox(fieldBox); + // Here we assume that int fields are always in 16 bit range, like ports + static_cast(widget)->setMinimum(0); + static_cast(widget)->setMaximum(65535); + static_cast(widget)->setValue(std::get<2>(fieldInfo).toInt()); + break; + case QVariant::String: + widget = new QLineEdit(std::get<2>(fieldInfo).toString(), fieldBox); + // Here we assume that fields named something with "password" are actual password inputs + if (std::get<0>(fieldInfo).toLower().contains("password")) + static_cast(widget)->setEchoMode(QLineEdit::Password); + break; + default: + qWarning() << "Unsupported type for backend property" << std::get<0>(fieldInfo); + } + if (widget) { + widget->setObjectName(std::get<0>(fieldInfo)); + formLayout->addRow(std::get<1>(fieldInfo) + ":", widget); + } + } + return fieldBox; +} -CoreConfigWizard::CoreConfigWizard(CoreConnection *connection, const QList &backends, QWidget *parent) - : QWizard(parent), - _connection(connection) +template +QVariantMap propertiesFromFieldWidgets(QGroupBox* fieldBox, const std::vector& fieldInfos) +{ + QVariantMap properties; + if (!fieldBox) + return properties; + + for (auto&& fieldInfo : fieldInfos) { + QString key = std::get<0>(fieldInfo); + QVariant value; + switch (std::get<2>(fieldInfo).type()) { + case QVariant::Int: { + auto* spinBox = fieldBox->findChild(key); + if (spinBox) + value = spinBox->value(); + else + qWarning() << "Could not find child widget for field" << key; + break; + } + case QVariant::String: { + auto* lineEdit = fieldBox->findChild(key); + if (lineEdit) + value = lineEdit->text(); + else + qWarning() << "Could not find child widget for field" << key; + break; + } + default: + qWarning() << "Unsupported type for backend property" << key; + } + properties[key] = std::move(value); + } + return properties; +} + +} // namespace + +CoreConfigWizard::CoreConfigWizard(CoreConnection* connection, const QVariantList& backendInfos, const QVariantList& authInfos, QWidget* parent) + : QWizard(parent) + , _connection{connection} { setModal(true); setAttribute(Qt::WA_DeleteOnClose); - foreach(const QVariant &v, backends) - _backends[v.toMap()["DisplayName"].toString()] = v; - setPage(IntroPage, new CoreConfigWizardPages::IntroPage(this)); setPage(AdminUserPage, new CoreConfigWizardPages::AdminUserPage(this)); - setPage(StorageSelectionPage, new CoreConfigWizardPages::StorageSelectionPage(_backends, this)); + setPage(AuthenticationSelectionPage, new CoreConfigWizardPages::AuthenticationSelectionPage(authInfos, this)); + setPage(StorageSelectionPage, new CoreConfigWizardPages::StorageSelectionPage(backendInfos, this)); syncPage = new CoreConfigWizardPages::SyncPage(this); - connect(syncPage, SIGNAL(setupCore(const QString &, const QVariantMap &)), SLOT(prepareCoreSetup(const QString &, const QVariantMap &))); + connect(syncPage, &CoreConfigWizardPages::SyncPage::setupCore, this, &CoreConfigWizard::prepareCoreSetup); setPage(SyncPage, syncPage); syncRelayPage = new CoreConfigWizardPages::SyncRelayPage(this); - connect(syncRelayPage, SIGNAL(startOver()), this, SLOT(startOver())); + connect(syncRelayPage, &CoreConfigWizardPages::SyncRelayPage::startOver, this, &CoreConfigWizard::startOver); setPage(SyncRelayPage, syncRelayPage); - //setPage(Page_StorageDetails, new StorageDetailsPage()); - //setPage(Page_Conclusion, new ConclusionPage(storageProviders)); setStartId(IntroPage); - //setStartId(StorageSelectionPage); #ifndef Q_OS_MAC setWizardStyle(ModernStyle); @@ -62,101 +153,108 @@ CoreConfigWizard::CoreConfigWizard(CoreConnection *connection, const QList(&CoreConnection::disconnectFromCore)); -QHash CoreConfigWizard::backends() const -{ - return _backends; + // Resize all pages to the size hint of the largest one, so the wizard is large enough + QSize maxSize; + for (int id : pageIds()) { + auto p = page(id); + p->adjustSize(); + maxSize = maxSize.expandedTo(p->sizeHint()); + } + for (int id : pageIds()) { + page(id)->setFixedSize(maxSize); + } } - -void CoreConfigWizard::prepareCoreSetup(const QString &backend, const QVariantMap &properties) +void CoreConfigWizard::prepareCoreSetup(const QString& backend, + const QVariantMap& properties, + const QString& authenticator, + const QVariantMap& authProperties) { // Prevent the user from changing any settings he already specified... - foreach(int idx, visitedPages()) - page(idx)->setEnabled(false); - - coreConnection()->setupCore(Protocol::SetupData(field("adminUser.user").toString(), field("adminUser.password").toString(), backend, properties)); + for (auto&& idx : visitedPages()) + page(idx)->setEnabled(false); + + // FIXME? We need to be able to set up older cores that don't have auth backend support. + // So if the core doesn't support that feature, don't pass those parameters. + if (!Client::isCoreFeatureEnabled(Quassel::Feature::Authenticators)) { + coreConnection()->setupCore( + Protocol::SetupData(field("adminUser.user").toString(), field("adminUser.password").toString(), backend, properties)); + } + else { + coreConnection()->setupCore(Protocol::SetupData(field("adminUser.user").toString(), + field("adminUser.password").toString(), + backend, + properties, + authenticator, + authProperties)); + } } - void CoreConfigWizard::coreSetupSuccess() { syncPage->setStatus(tr("Your core has been successfully configured. Logging you in...")); syncPage->setError(false); - syncRelayPage->setMode(CoreConfigWizardPages::SyncRelayPage::Error); - coreConnection()->loginToCore(field("adminUser.user").toString(), field("adminUser.password").toString(), field("adminUser.rememberPasswd").toBool()); + syncRelayPage->setMode(CoreConfigWizardPages::SyncRelayPage::Success); + coreConnection()->loginToCore(field("adminUser.user").toString(), + field("adminUser.password").toString(), + field("adminUser.rememberPasswd").toBool()); } - -void CoreConfigWizard::coreSetupFailed(const QString &error) +void CoreConfigWizard::coreSetupFailed(const QString& error) { syncPage->setStatus(tr("Core configuration failed:
%1
Press Next to start over.").arg(error)); syncPage->setError(true); syncRelayPage->setMode(CoreConfigWizardPages::SyncRelayPage::Error); - //foreach(int idx, visitedPages()) page(idx)->setEnabled(true); - //setStartId(SyncPage); - //restart(); + // foreach(int idx, visitedPages()) page(idx)->setEnabled(true); + // setStartId(SyncPage); + // restart(); } - void CoreConfigWizard::startOver() { - foreach(int idx, visitedPages()) page(idx)->setEnabled(true); + foreach (int idx, visitedPages()) + page(idx)->setEnabled(true); setStartId(CoreConfigWizard::AdminUserPage); restart(); } - -void CoreConfigWizard::loginSuccess() -{ - syncPage->setStatus(tr("Your are now logged into your freshly configured Quassel Core!
" - "Please remember to configure your identities and networks now.")); - syncPage->setComplete(true); - syncPage->setFinalPage(true); -} - - void CoreConfigWizard::syncFinished() { accept(); } - namespace CoreConfigWizardPages { /*** Intro Page ***/ -IntroPage::IntroPage(QWidget *parent) : QWizardPage(parent) +IntroPage::IntroPage(QWidget* parent) + : QWizardPage(parent) { ui.setupUi(this); setTitle(tr("Introduction")); - //setSubTitle(tr("foobar")); - //setPixmap(QWizard::WatermarkPixmap, QPixmap(":icons/quassel-icon.png")); + // setSubTitle(tr("foobar")); + // setPixmap(QWizard::WatermarkPixmap, QPixmap(":icons/quassel-icon.png")); } - int IntroPage::nextId() const { return CoreConfigWizard::AdminUserPage; } - /*** Admin User Page ***/ -AdminUserPage::AdminUserPage(QWidget *parent) : QWizardPage(parent) +AdminUserPage::AdminUserPage(QWidget* parent) + : QWizardPage(parent) { ui.setupUi(this); setTitle(tr("Create Admin User")); @@ -166,259 +264,270 @@ AdminUserPage::AdminUserPage(QWidget *parent) : QWizardPage(parent) registerField("adminUser.password*", ui.password); registerField("adminUser.password2*", ui.password2); registerField("adminUser.rememberPasswd", ui.rememberPasswd); - - //ui.user->setText("foo"); - //ui.password->setText("foo"); - //ui.password2->setText("foo"); } - int AdminUserPage::nextId() const { - return CoreConfigWizard::StorageSelectionPage; + // If the core doesn't support auth backends, skip that page! + if (!Client::isCoreFeatureEnabled(Quassel::Feature::Authenticators)) { + return CoreConfigWizard::StorageSelectionPage; + } + else { + return CoreConfigWizard::AuthenticationSelectionPage; + } } - bool AdminUserPage::isComplete() const { bool ok = !ui.user->text().isEmpty() && !ui.password->text().isEmpty() && ui.password->text() == ui.password2->text(); return ok; } +/*** Authentication Selection Page ***/ -/*** Storage Selection Page ***/ - -StorageSelectionPage::StorageSelectionPage(const QHash &backends, QWidget *parent) - : QWizardPage(parent), - _connectionBox(0), - _backends(backends) +AuthenticationSelectionPage::AuthenticationSelectionPage(const QVariantList& authInfos, QWidget* parent) + : QWizardPage(parent) { ui.setupUi(this); - setTitle(tr("Select Storage Backend")); - setSubTitle(tr("Please select a database backend for the Quassel Core storage to store the backlog and other data in.")); - setCommitPage(true); + setTitle(tr("Select Authentication Backend")); + setSubTitle(tr("Please select a backend for Quassel Core to use for authenticating users.")); - registerField("storage.backend", ui.backendList); + registerField("authentication.backend", ui.backendList); + + for (auto&& authInfo : authInfos) { + auto props = authInfo.toMap(); + // Extract field infos to avoid having to reparse the list + std::vector fields; + const auto& list = props["SetupData"].toList(); + for (int i = 0; i + 2 < list.size(); i += 3) { + fields.emplace_back(std::make_tuple(list[i].toString(), list[i + 1].toString(), list[i + 2])); + } + props.remove("SetupData"); + + _authProperties.emplace_back(std::move(props)); + _authFields.emplace_back(std::move(fields)); - foreach(QString key, _backends.keys()) { - ui.backendList->addItem(_backends[key].toMap()["DisplayName"].toString(), key); + // Create widgets + ui.backendList->addItem(_authProperties.back()["DisplayName"].toString(), _authProperties.back()["BackendId"].toString()); + ui.descriptionStack->addWidget(createDescriptionBox(_authProperties.back()["Description"].toString())); + ui.authSettingsStack->addWidget(createFieldBox(tr("Authentication Settings"), _authFields.back())); } - on_backendList_currentIndexChanged(); + // Do some trickery to make the page large enough + setSizePolicy({QSizePolicy::Fixed, QSizePolicy::Fixed}); + + QSizePolicy sp{QSizePolicy::MinimumExpanding, QSizePolicy::Fixed}; + sp.setRetainSizeWhenHidden(true); + ui.descriptionStack->setSizePolicy(sp); + ui.authSettingsStack->setSizePolicy(sp); + + ui.descriptionStack->adjustSize(); + ui.authSettingsStack->adjustSize(); + + ui.backendList->setCurrentIndex(0); } +int AuthenticationSelectionPage::nextId() const +{ + return CoreConfigWizard::StorageSelectionPage; +} -int StorageSelectionPage::nextId() const +QString AuthenticationSelectionPage::displayName() const { - return CoreConfigWizard::SyncPage; + return ui.backendList->currentText(); } +QString AuthenticationSelectionPage::authenticator() const +{ + return ui.backendList->currentData().toString(); +} -QString StorageSelectionPage::selectedBackend() const +QVariantMap AuthenticationSelectionPage::authProperties() const { - return ui.backendList->currentText(); + return propertiesFromFieldWidgets(qobject_cast(ui.authSettingsStack->currentWidget()), + _authFields[ui.backendList->currentIndex()]); +} + +void AuthenticationSelectionPage::on_backendList_currentIndexChanged(int index) +{ + ui.descriptionStack->setCurrentIndex(index); + ui.authSettingsStack->setCurrentIndex(index); + ui.authSettingsStack->setVisible(!_authFields[index].empty()); } +/*** Storage Selection Page ***/ -QVariantMap StorageSelectionPage::connectionProperties() const +StorageSelectionPage::StorageSelectionPage(const QVariantList& backendInfos, QWidget* parent) + : QWizardPage(parent) { - QString backend = ui.backendList->itemData(ui.backendList->currentIndex()).toString(); + ui.setupUi(this); - QVariantMap properties; - QStringList setupKeys = _backends[backend].toMap()["SetupKeys"].toStringList(); - if (!setupKeys.isEmpty()) { - QVariantMap defaults = _backends[backend].toMap()["SetupDefaults"].toMap(); - foreach(QString key, setupKeys) { - QWidget *widget = _connectionBox->findChild(key); - QVariant def; - if (defaults.contains(key)) { - def = defaults[key]; - } - switch (def.type()) { - case QVariant::Int: - { - QSpinBox *spinbox = qobject_cast(widget); - Q_ASSERT(spinbox); - def = QVariant(spinbox->value()); + setTitle(tr("Select Storage Backend")); + setSubTitle(tr("Please select a storage backend for Quassel Core.")); + setCommitPage(true); + + registerField("storage.backend", ui.backendList); + + int defaultIndex{0}; // Legacy cores send backend infos in arbitrary order + + for (auto&& backendInfo : backendInfos) { + auto props = backendInfo.toMap(); + // Extract field infos to avoid having to reparse the list + std::vector fields; + + // Legacy cores (prior to 0.13) didn't send SetupData for storage backends; deal with this + if (!props.contains("SetupData")) { + const auto& defaultValues = props["SetupDefaults"].toMap(); + for (auto&& key : props["SetupKeys"].toStringList()) { + fields.emplace_back(std::make_tuple(key, key, defaultValues.value(key, QString{}))); } - break; - default: - { - QLineEdit *lineEdit = qobject_cast(widget); - Q_ASSERT(lineEdit); - def = QVariant(lineEdit->text()); + if (props.value("IsDefault", false).toBool()) { + defaultIndex = ui.backendList->count(); } + } + else { + const auto& list = props["SetupData"].toList(); + for (int i = 0; i + 2 < list.size(); i += 3) { + fields.emplace_back(std::make_tuple(list[i].toString(), list[i + 1].toString(), list[i + 2])); } - properties[key] = def; + props.remove("SetupData"); } + props.remove("SetupKeys"); + props.remove("SetupDefaults"); + // Legacy cores (prior to 0.13) don't send the BackendId property + if (!props.contains("BackendId")) + props["BackendId"] = props["DisplayName"]; + _backendProperties.emplace_back(std::move(props)); + _backendFields.emplace_back(std::move(fields)); + + // Create widgets + ui.backendList->addItem(_backendProperties.back()["DisplayName"].toString(), _backendProperties.back()["BackendId"].toString()); + ui.descriptionStack->addWidget(createDescriptionBox(_backendProperties.back()["Description"].toString())); + ui.storageSettingsStack->addWidget(createFieldBox(tr("Storage Settings"), _backendFields.back())); } - qDebug() << properties; - -// QVariantMap properties = _backends[backend].toMap()["ConnectionProperties"].toMap(); -// if(!properties.isEmpty() && _connectionBox) { -// QVariantMap::iterator propertyIter = properties.begin(); -// while(propertyIter != properties.constEnd()) { -// QWidget *widget = _connectionBox->findChild(propertyIter.key()); -// switch(propertyIter.value().type()) { -// case QVariant::Int: -// { -// QSpinBox *spinbox = qobject_cast(widget); -// Q_ASSERT(spinbox); -// propertyIter.value() = QVariant(spinbox->value()); -// } -// break; -// default: -// { -// QLineEdit *lineEdit = qobject_cast(widget); -// Q_ASSERT(lineEdit); -// propertyIter.value() = QVariant(lineEdit->text()); -// } -// } -// propertyIter++; -// } -// } - return properties; + + // Do some trickery to make the page large enough + setSizePolicy({QSizePolicy::Fixed, QSizePolicy::Fixed}); + + QSizePolicy sp{QSizePolicy::MinimumExpanding, QSizePolicy::Fixed}; + sp.setRetainSizeWhenHidden(true); + ui.descriptionStack->setSizePolicy(sp); + ui.storageSettingsStack->setSizePolicy(sp); + + ui.descriptionStack->adjustSize(); + ui.storageSettingsStack->adjustSize(); + + ui.backendList->setCurrentIndex(defaultIndex); } +int StorageSelectionPage::nextId() const +{ + return CoreConfigWizard::SyncPage; +} -void StorageSelectionPage::on_backendList_currentIndexChanged() +QString StorageSelectionPage::displayName() const { - QString backend = ui.backendList->itemData(ui.backendList->currentIndex()).toString(); - ui.description->setText(_backends[backend].toMap()["Description"].toString()); + return ui.backendList->currentText(); +} - if (_connectionBox) { - layout()->removeWidget(_connectionBox); - _connectionBox->deleteLater(); - _connectionBox = 0; - } +QString StorageSelectionPage::backend() const +{ + return ui.backendList->currentData().toString(); +} - QStringList setupKeys = _backends[backend].toMap()["SetupKeys"].toStringList(); - if (!setupKeys.isEmpty()) { - QVariantMap defaults = _backends[backend].toMap()["SetupDefaults"].toMap(); - QGroupBox *propertyBox = new QGroupBox(this); - propertyBox->setTitle(tr("Connection Properties")); - QFormLayout *formlayout = new QFormLayout; - - foreach(QString key, setupKeys) { - QWidget *widget = 0; - QVariant def; - if (defaults.contains(key)) { - def = defaults[key]; - } - switch (def.type()) { - case QVariant::Int: - { - QSpinBox *spinbox = new QSpinBox(propertyBox); - spinbox->setMaximum(64000); - spinbox->setValue(def.toInt()); - widget = spinbox; - } - break; - default: - { - QLineEdit *lineEdit = new QLineEdit(def.toString(), propertyBox); - if (key.toLower().contains("password")) { - lineEdit->setEchoMode(QLineEdit::Password); - } - widget = lineEdit; - } - } - widget->setObjectName(key); - formlayout->addRow(key + ":", widget); - } - propertyBox->setLayout(formlayout); - static_cast(layout())->insertWidget(layout()->indexOf(ui.descriptionBox) + 1, propertyBox); - _connectionBox = propertyBox; - } +QVariantMap StorageSelectionPage::backendProperties() const +{ + return propertiesFromFieldWidgets(qobject_cast(ui.storageSettingsStack->currentWidget()), + _backendFields[ui.backendList->currentIndex()]); } +void StorageSelectionPage::on_backendList_currentIndexChanged(int index) +{ + ui.descriptionStack->setCurrentIndex(index); + ui.storageSettingsStack->setCurrentIndex(index); + ui.storageSettingsStack->setVisible(!_backendFields[index].empty()); +} /*** Sync Page ***/ -SyncPage::SyncPage(QWidget *parent) : QWizardPage(parent) +SyncPage::SyncPage(QWidget* parent) + : QWizardPage(parent) { ui.setupUi(this); setTitle(tr("Storing Your Settings")); - setSubTitle(tr("Your settings are now stored in the core, and you will be logged in automatically.")); + setSubTitle(tr("Your settings are now being stored in the core, and you will be logged in automatically.")); } - void SyncPage::initializePage() { - complete = false; - hasError = false; + _complete = false; + _hasError = false; + emit completeChanged(); + + // Fill in sync info about the storage layer. + auto* storagePage = qobject_cast(wizard()->page(CoreConfigWizard::StorageSelectionPage)); + QString backend = storagePage->backend(); + QVariantMap backendProperties = storagePage->backendProperties(); + ui.backend->setText(storagePage->displayName()); + + // Fill in sync info about the authentication layer. + auto* authPage = qobject_cast(wizard()->page(CoreConfigWizard::AuthenticationSelectionPage)); + QString authenticator = authPage->authenticator(); + QVariantMap authProperties = authPage->authProperties(); + ui.authenticator->setText(authPage->displayName()); - StorageSelectionPage *storagePage = qobject_cast(wizard()->page(CoreConfigWizard::StorageSelectionPage)); - QString backend = storagePage->selectedBackend(); - QVariantMap properties = storagePage->connectionProperties(); - Q_ASSERT(!backend.isEmpty()); ui.user->setText(wizard()->field("adminUser.user").toString()); - ui.backend->setText(backend); - emit setupCore(backend, properties); -} + emit setupCore(backend, backendProperties, authenticator, authProperties); +} int SyncPage::nextId() const { - if (!hasError) return -1; + if (!_hasError) + return -1; return CoreConfigWizard::SyncRelayPage; } - bool SyncPage::isComplete() const { - return complete; + return _complete || _hasError; } - -void SyncPage::setStatus(const QString &status) +void SyncPage::setStatus(const QString& status) { ui.status->setText(status); } - void SyncPage::setError(bool e) { - hasError = e; + _hasError = e; + setFinalPage(!e); + emit completeChanged(); } - void SyncPage::setComplete(bool c) { - complete = c; + _complete = c; completeChanged(); } - /*** Sync Relay Page ***/ -SyncRelayPage::SyncRelayPage(QWidget *parent) : QWizardPage(parent) +SyncRelayPage::SyncRelayPage(QWidget* parent) + : QWizardPage(parent) { mode = Success; } - void SyncRelayPage::setMode(Mode m) { mode = m; } - -/* -void SyncRelayPage::initializePage() { - return; - if(mode == Success) { - wizard()->accept(); - } else { - emit startOver(); - } -} -*/ - int SyncRelayPage::nextId() const { emit startOver(); return 0; } -}; /* namespace CoreConfigWizardPages */ +} /* namespace CoreConfigWizardPages */