X-Git-Url: https://git.quassel-irc.org/?p=quassel.git;a=blobdiff_plain;f=src%2Fqtui%2Fcoreconfigwizard.cpp;h=e8305123865ae1f17569f7c37df85a700adde3d5;hp=63ac400383696aef5b3b7b1447501f1539e98d59;hb=6eefdfc697067d184a589fc8a231b16316c09106;hpb=0a43227b8cd44625f4881cc1545d42c8c8a4876c diff --git a/src/qtui/coreconfigwizard.cpp b/src/qtui/coreconfigwizard.cpp index 63ac4003..e8305123 100644 --- a/src/qtui/coreconfigwizard.cpp +++ b/src/qtui/coreconfigwizard.cpp @@ -1,5 +1,5 @@ /*************************************************************************** - * Copyright (C) 2005-2016 by the Quassel Project * + * Copyright (C) 2005-2018 by the Quassel Project * * devel@quassel-irc.org * * * * This program is free software; you can redistribute it and/or modify * @@ -18,39 +18,134 @@ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. * ***************************************************************************/ +#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) +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; +} + +} // anon + + +CoreConfigWizard::CoreConfigWizard(CoreConnection *connection, const QVariantList &backendInfos, const QVariantList &authInfos, QWidget *parent) : QWizard(parent), - _connection(connection) + _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,34 +157,45 @@ 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); + for (auto &&idx : visitedPages()) + page(idx)->setEnabled(false); - coreConnection()->setupCore(Protocol::SetupData(field("adminUser.user").toString(), field("adminUser.password").toString(), backend, properties)); + // 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)); + } } @@ -97,7 +203,7 @@ void CoreConfigWizard::coreSetupSuccess() { syncPage->setStatus(tr("Your core has been successfully configured. Logging you in...")); syncPage->setError(false); - syncRelayPage->setMode(CoreConfigWizardPages::SyncRelayPage::Error); + syncRelayPage->setMode(CoreConfigWizardPages::SyncRelayPage::Success); coreConnection()->loginToCore(field("adminUser.user").toString(), field("adminUser.password").toString(), field("adminUser.rememberPasswd").toBool()); } @@ -121,15 +227,6 @@ void CoreConfigWizard::startOver() } -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(); @@ -166,16 +263,18 @@ 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; + } } @@ -185,27 +284,147 @@ bool AdminUserPage::isComplete() const return ok; } +/*** Authentication Selection Page ***/ + +AuthenticationSelectionPage::AuthenticationSelectionPage(const QVariantList &authInfos, QWidget *parent) + : QWizardPage(parent) +{ + ui.setupUi(this); + + setTitle(tr("Select Authentication Backend")); + setSubTitle(tr("Please select a backend for Quassel Core to use for authenticating users.")); + + 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)); + + // 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())); + } + + // 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; +} + + +QString AuthenticationSelectionPage::displayName() const +{ + return ui.backendList->currentText(); +} + + +QString AuthenticationSelectionPage::authenticator() const +{ + return ui.backendList->currentData().toString(); +} + + +QVariantMap AuthenticationSelectionPage::authProperties() const +{ + 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 ***/ -StorageSelectionPage::StorageSelectionPage(const QHash &backends, QWidget *parent) - : QWizardPage(parent), - _connectionBox(0), - _backends(backends) +StorageSelectionPage::StorageSelectionPage(const QVariantList &backendInfos, 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.")); + setSubTitle(tr("Please select a storage backend for Quassel Core.")); setCommitPage(true); registerField("storage.backend", ui.backendList); - foreach(QString key, _backends.keys()) { - ui.backendList->addItem(_backends[key].toMap()["DisplayName"].toString(), key); + 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{}))); + } + 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])); + } + 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())); } - 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.storageSettingsStack->setSizePolicy(sp); + + ui.descriptionStack->adjustSize(); + ui.storageSettingsStack->adjustSize(); + + ui.backendList->setCurrentIndex(defaultIndex); } @@ -215,122 +434,30 @@ int StorageSelectionPage::nextId() const } -QString StorageSelectionPage::selectedBackend() const +QString StorageSelectionPage::displayName() const { return ui.backendList->currentText(); } -QVariantMap StorageSelectionPage::connectionProperties() const +QString StorageSelectionPage::backend() const { - QString backend = ui.backendList->itemData(ui.backendList->currentIndex()).toString(); - - 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()); - } - break; - default: - { - QLineEdit *lineEdit = qobject_cast(widget); - Q_ASSERT(lineEdit); - def = QVariant(lineEdit->text()); - } - } - properties[key] = def; - } - } - 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; + return ui.backendList->currentData().toString(); } -void StorageSelectionPage::on_backendList_currentIndexChanged() +QVariantMap StorageSelectionPage::backendProperties() const { - QString backend = ui.backendList->itemData(ui.backendList->currentIndex()).toString(); - ui.description->setText(_backends[backend].toMap()["Description"].toString()); + return propertiesFromFieldWidgets(qobject_cast(ui.storageSettingsStack->currentWidget()), + _backendFields[ui.backendList->currentIndex()]); +} - if (_connectionBox) { - layout()->removeWidget(_connectionBox); - _connectionBox->deleteLater(); - _connectionBox = 0; - } - 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; - } +void StorageSelectionPage::on_backendList_currentIndexChanged(int index) +{ + ui.descriptionStack->setCurrentIndex(index); + ui.storageSettingsStack->setCurrentIndex(index); + ui.storageSettingsStack->setVisible(!_backendFields[index].empty()); } @@ -340,35 +467,45 @@ 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; } @@ -380,13 +517,15 @@ void SyncPage::setStatus(const QString &status) void SyncPage::setError(bool e) { - hasError = e; + _hasError = e; + setFinalPage(!e); + emit completeChanged(); } void SyncPage::setComplete(bool c) { - complete = c; + _complete = c; completeChanged(); } @@ -404,21 +543,9 @@ 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 */