1 /***************************************************************************
2 * Copyright (C) 2005-2018 by the Quassel Project *
3 * devel@quassel-irc.org *
5 * This program is free software; you can redistribute it and/or modify *
6 * it under the terms of the GNU General Public License as published by *
7 * the Free Software Foundation; either version 2 of the License, or *
8 * (at your option) version 3. *
10 * This program is distributed in the hope that it will be useful, *
11 * but WITHOUT ANY WARRANTY; without even the implied warranty of *
12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
13 * GNU General Public License for more details. *
15 * You should have received a copy of the GNU General Public License *
16 * along with this program; if not, write to the *
17 * Free Software Foundation, Inc., *
18 * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. *
19 ***************************************************************************/
21 #include "coreconfigwizard.h"
24 #include <QAbstractButton>
25 #include <QCoreApplication>
26 #include <QFormLayout>
30 #include "coreconnection.h"
35 QGroupBox *createDescriptionBox(const QString &description)
37 auto box = new QGroupBox;
38 auto layout = new QVBoxLayout(box);
39 auto label = new QLabel(description, box);
40 label->setWordWrap(true);
41 layout->addWidget(label);
42 layout->setAlignment(label, Qt::AlignTop);
43 box->setTitle(QCoreApplication::translate("CoreConfigWizard", "Description"));
48 template<typename FieldInfo>
49 QGroupBox *createFieldBox(const QString &title, const std::vector<FieldInfo> &fieldInfos)
51 // Create a config UI based on the field types sent from the backend
52 // We make some assumptions here (like integer range and password field names) that may not
53 // hold true for future authenticator types - but the only way around it for now would be to
54 // provide specialized config widgets for those (which may be a good idea anyway, e.g. if we
55 // think about client-side translations...)
57 auto *fieldBox = new QGroupBox;
58 fieldBox->setTitle(title);
59 auto *formLayout = new QFormLayout;
60 fieldBox->setLayout(formLayout);
62 for (auto &&fieldInfo : fieldInfos) {
63 QWidget *widget {nullptr};
64 switch (std::get<2>(fieldInfo).type()) {
66 widget = new QSpinBox(fieldBox);
67 // Here we assume that int fields are always in 16 bit range, like ports
68 static_cast<QSpinBox *>(widget)->setMinimum(0);
69 static_cast<QSpinBox *>(widget)->setMaximum(65535);
70 static_cast<QSpinBox *>(widget)->setValue(std::get<2>(fieldInfo).toInt());
72 case QVariant::String:
73 widget = new QLineEdit(std::get<2>(fieldInfo).toString(), fieldBox);
74 // Here we assume that fields named something with "password" are actual password inputs
75 if (std::get<0>(fieldInfo).toLower().contains("password"))
76 static_cast<QLineEdit *>(widget)->setEchoMode(QLineEdit::Password);
79 qWarning() << "Unsupported type for backend property" << std::get<0>(fieldInfo);
82 widget->setObjectName(std::get<0>(fieldInfo));
83 formLayout->addRow(std::get<1>(fieldInfo) + ":", widget);
90 template<typename FieldInfo>
91 QVariantMap propertiesFromFieldWidgets(QGroupBox *fieldBox, const std::vector<FieldInfo> &fieldInfos)
93 QVariantMap properties;
97 for (auto &&fieldInfo : fieldInfos) {
98 QString key = std::get<0>(fieldInfo);
100 switch (std::get<2>(fieldInfo).type()) {
101 case QVariant::Int: {
102 QSpinBox *spinBox = fieldBox->findChild<QSpinBox *>(key);
104 value = spinBox->value();
106 qWarning() << "Could not find child widget for field" << key;
109 case QVariant::String: {
110 QLineEdit *lineEdit = fieldBox->findChild<QLineEdit *>(key);
112 value = lineEdit->text();
114 qWarning() << "Could not find child widget for field" << key;
118 qWarning() << "Unsupported type for backend property" << key;
120 properties[key] = std::move(value);
128 CoreConfigWizard::CoreConfigWizard(CoreConnection *connection, const QVariantList &backendInfos, const QVariantList &authInfos, QWidget *parent)
130 _connection{connection}
133 setAttribute(Qt::WA_DeleteOnClose);
135 setPage(IntroPage, new CoreConfigWizardPages::IntroPage(this));
136 setPage(AdminUserPage, new CoreConfigWizardPages::AdminUserPage(this));
137 setPage(AuthenticationSelectionPage, new CoreConfigWizardPages::AuthenticationSelectionPage(authInfos, this));
138 setPage(StorageSelectionPage, new CoreConfigWizardPages::StorageSelectionPage(backendInfos, this));
139 syncPage = new CoreConfigWizardPages::SyncPage(this);
140 connect(syncPage, SIGNAL(setupCore(const QString &, const QVariantMap &, const QString &, const QVariantMap &)),
141 SLOT(prepareCoreSetup(const QString &, const QVariantMap &, const QString &, const QVariantMap &)));
142 setPage(SyncPage, syncPage);
143 syncRelayPage = new CoreConfigWizardPages::SyncRelayPage(this);
144 connect(syncRelayPage, SIGNAL(startOver()), this, SLOT(startOver()));
145 setPage(SyncRelayPage, syncRelayPage);
147 setStartId(IntroPage);
150 setWizardStyle(ModernStyle);
153 setOption(HaveHelpButton, false);
154 setOption(NoBackButtonOnStartPage, true);
155 setOption(HaveNextButtonOnLastPage, false);
156 setOption(HaveFinishButtonOnEarlyPages, false);
157 setOption(NoCancelButton, true);
158 setOption(IndependentPages, true);
162 setWindowTitle(CoreConfigWizard::tr("Core Configuration Wizard"));
163 setPixmap(QWizard::LogoPixmap, icon::get("quassel").pixmap(48));
165 connect(connection, SIGNAL(coreSetupSuccess()), SLOT(coreSetupSuccess()));
166 connect(connection, SIGNAL(coreSetupFailed(QString)), SLOT(coreSetupFailed(QString)));
167 connect(connection, SIGNAL(synchronized()), SLOT(syncFinished()));
168 connect(this, SIGNAL(rejected()), connection, SLOT(disconnectFromCore()));
171 // Resize all pages to the size hint of the largest one, so the wizard is large enough
173 for (int id : pageIds()) {
176 maxSize = maxSize.expandedTo(p->sizeHint());
178 for (int id : pageIds()) {
179 page(id)->setFixedSize(maxSize);
184 void CoreConfigWizard::prepareCoreSetup(const QString &backend, const QVariantMap &properties, const QString &authenticator, const QVariantMap &authProperties)
186 // Prevent the user from changing any settings he already specified...
187 for (auto &&idx : visitedPages())
188 page(idx)->setEnabled(false);
190 // FIXME? We need to be able to set up older cores that don't have auth backend support.
191 // So if the core doesn't support that feature, don't pass those parameters.
192 if (!Client::isCoreFeatureEnabled(Quassel::Feature::Authenticators)) {
193 coreConnection()->setupCore(Protocol::SetupData(field("adminUser.user").toString(), field("adminUser.password").toString(), backend, properties));
196 coreConnection()->setupCore(Protocol::SetupData(field("adminUser.user").toString(), field("adminUser.password").toString(), backend, properties, authenticator, authProperties));
201 void CoreConfigWizard::coreSetupSuccess()
203 syncPage->setStatus(tr("Your core has been successfully configured. Logging you in..."));
204 syncPage->setError(false);
205 syncRelayPage->setMode(CoreConfigWizardPages::SyncRelayPage::Success);
206 coreConnection()->loginToCore(field("adminUser.user").toString(), field("adminUser.password").toString(), field("adminUser.rememberPasswd").toBool());
210 void CoreConfigWizard::coreSetupFailed(const QString &error)
212 syncPage->setStatus(tr("Core configuration failed:<br><b>%1</b><br>Press <em>Next</em> to start over.").arg(error));
213 syncPage->setError(true);
214 syncRelayPage->setMode(CoreConfigWizardPages::SyncRelayPage::Error);
215 //foreach(int idx, visitedPages()) page(idx)->setEnabled(true);
216 //setStartId(SyncPage);
221 void CoreConfigWizard::startOver()
223 foreach(int idx, visitedPages()) page(idx)->setEnabled(true);
224 setStartId(CoreConfigWizard::AdminUserPage);
229 void CoreConfigWizard::syncFinished()
235 namespace CoreConfigWizardPages {
238 IntroPage::IntroPage(QWidget *parent) : QWizardPage(parent)
241 setTitle(tr("Introduction"));
242 //setSubTitle(tr("foobar"));
243 //setPixmap(QWizard::WatermarkPixmap, QPixmap(":icons/quassel-icon.png"));
247 int IntroPage::nextId() const
249 return CoreConfigWizard::AdminUserPage;
253 /*** Admin User Page ***/
255 AdminUserPage::AdminUserPage(QWidget *parent) : QWizardPage(parent)
258 setTitle(tr("Create Admin User"));
259 setSubTitle(tr("First, we will create a user on the core. This first user will have administrator privileges."));
261 registerField("adminUser.user*", ui.user);
262 registerField("adminUser.password*", ui.password);
263 registerField("adminUser.password2*", ui.password2);
264 registerField("adminUser.rememberPasswd", ui.rememberPasswd);
268 int AdminUserPage::nextId() const
270 // If the core doesn't support auth backends, skip that page!
271 if (!Client::isCoreFeatureEnabled(Quassel::Feature::Authenticators)) {
272 return CoreConfigWizard::StorageSelectionPage;
275 return CoreConfigWizard::AuthenticationSelectionPage;
280 bool AdminUserPage::isComplete() const
282 bool ok = !ui.user->text().isEmpty() && !ui.password->text().isEmpty() && ui.password->text() == ui.password2->text();
286 /*** Authentication Selection Page ***/
288 AuthenticationSelectionPage::AuthenticationSelectionPage(const QVariantList &authInfos, QWidget *parent)
289 : QWizardPage(parent)
293 setTitle(tr("Select Authentication Backend"));
294 setSubTitle(tr("Please select a backend for Quassel Core to use for authenticating users."));
296 registerField("authentication.backend", ui.backendList);
298 for (auto &&authInfo : authInfos) {
299 auto props = authInfo.toMap();
300 // Extract field infos to avoid having to reparse the list
301 std::vector<FieldInfo> fields;
302 const auto &list = props["SetupData"].toList();
303 for (int i = 0; i + 2 < list.size(); i += 3) {
304 fields.emplace_back(std::make_tuple(list[i].toString(), list[i+1].toString(), list[i+2]));
306 props.remove("SetupData");
308 _authProperties.emplace_back(std::move(props));
309 _authFields.emplace_back(std::move(fields));
312 ui.backendList->addItem(_authProperties.back()["DisplayName"].toString(), _authProperties.back()["BackendId"].toString());
313 ui.descriptionStack->addWidget(createDescriptionBox(_authProperties.back()["Description"].toString()));
314 ui.authSettingsStack->addWidget(createFieldBox(tr("Authentication Settings"), _authFields.back()));
317 // Do some trickery to make the page large enough
318 setSizePolicy({QSizePolicy::Fixed, QSizePolicy::Fixed});
320 QSizePolicy sp{QSizePolicy::MinimumExpanding, QSizePolicy::Fixed};
321 #if QT_VERSION >= 0x050200
322 sp.setRetainSizeWhenHidden(true);
324 ui.authSettingsStack->setVisible(true); // ugly hack that will show an empty box, but we'll deprecate Qt4 soon anyway
326 ui.descriptionStack->setSizePolicy(sp);
327 ui.authSettingsStack->setSizePolicy(sp);
329 ui.descriptionStack->adjustSize();
330 ui.authSettingsStack->adjustSize();
332 ui.backendList->setCurrentIndex(0);
336 int AuthenticationSelectionPage::nextId() const
338 return CoreConfigWizard::StorageSelectionPage;
342 QString AuthenticationSelectionPage::displayName() const
344 return ui.backendList->currentText();
348 QString AuthenticationSelectionPage::authenticator() const
350 #if QT_VERSION >= 0x050200
351 return ui.backendList->currentData().toString();
353 return ui.backendList->itemData(ui.backendList->currentIndex()).toString();
358 QVariantMap AuthenticationSelectionPage::authProperties() const
360 return propertiesFromFieldWidgets(qobject_cast<QGroupBox *>(ui.authSettingsStack->currentWidget()),
361 _authFields[ui.backendList->currentIndex()]);
365 void AuthenticationSelectionPage::on_backendList_currentIndexChanged(int index)
367 ui.descriptionStack->setCurrentIndex(index);
368 ui.authSettingsStack->setCurrentIndex(index);
369 ui.authSettingsStack->setVisible(!_authFields[index].empty());
372 /*** Storage Selection Page ***/
374 StorageSelectionPage::StorageSelectionPage(const QVariantList &backendInfos, QWidget *parent)
375 : QWizardPage(parent)
379 setTitle(tr("Select Storage Backend"));
380 setSubTitle(tr("Please select a storage backend for Quassel Core."));
383 registerField("storage.backend", ui.backendList);
385 int defaultIndex {0}; // Legacy cores send backend infos in arbitrary order
387 for (auto &&backendInfo : backendInfos) {
388 auto props = backendInfo.toMap();
389 // Extract field infos to avoid having to reparse the list
390 std::vector<FieldInfo> fields;
392 // Legacy cores (prior to 0.13) didn't send SetupData for storage backends; deal with this
393 if (!props.contains("SetupData")) {
394 const auto &defaultValues = props["SetupDefaults"].toMap();
395 for (auto &&key : props["SetupKeys"].toStringList()) {
396 fields.emplace_back(std::make_tuple(key, key, defaultValues.value(key, QString{})));
398 if (props.value("IsDefault", false).toBool()) {
399 defaultIndex = ui.backendList->count();
403 const auto &list = props["SetupData"].toList();
404 for (int i = 0; i + 2 < list.size(); i += 3) {
405 fields.emplace_back(std::make_tuple(list[i].toString(), list[i+1].toString(), list[i+2]));
407 props.remove("SetupData");
409 props.remove("SetupKeys");
410 props.remove("SetupDefaults");
411 // Legacy cores (prior to 0.13) don't send the BackendId property
412 if (!props.contains("BackendId"))
413 props["BackendId"] = props["DisplayName"];
414 _backendProperties.emplace_back(std::move(props));
415 _backendFields.emplace_back(std::move(fields));
418 ui.backendList->addItem(_backendProperties.back()["DisplayName"].toString(), _backendProperties.back()["BackendId"].toString());
419 ui.descriptionStack->addWidget(createDescriptionBox(_backendProperties.back()["Description"].toString()));
420 ui.storageSettingsStack->addWidget(createFieldBox(tr("Storage Settings"), _backendFields.back()));
423 // Do some trickery to make the page large enough
424 setSizePolicy({QSizePolicy::Fixed, QSizePolicy::Fixed});
426 QSizePolicy sp{QSizePolicy::MinimumExpanding, QSizePolicy::Fixed};
427 #if QT_VERSION >= 0x050200
428 sp.setRetainSizeWhenHidden(true);
430 ui.storageSettingsStack->setVisible(true); // ugly hack that will show an empty box, but we'll deprecate Qt4 soon anyway
432 ui.descriptionStack->setSizePolicy(sp);
433 ui.storageSettingsStack->setSizePolicy(sp);
435 ui.descriptionStack->adjustSize();
436 ui.storageSettingsStack->adjustSize();
438 ui.backendList->setCurrentIndex(defaultIndex);
442 int StorageSelectionPage::nextId() const
444 return CoreConfigWizard::SyncPage;
448 QString StorageSelectionPage::displayName() const
450 return ui.backendList->currentText();
454 QString StorageSelectionPage::backend() const
456 #if QT_VERSION >= 0x050200
457 return ui.backendList->currentData().toString();
459 return ui.backendList->itemData(ui.backendList->currentIndex()).toString();
464 QVariantMap StorageSelectionPage::backendProperties() const
466 return propertiesFromFieldWidgets(qobject_cast<QGroupBox *>(ui.storageSettingsStack->currentWidget()),
467 _backendFields[ui.backendList->currentIndex()]);
471 void StorageSelectionPage::on_backendList_currentIndexChanged(int index)
473 ui.descriptionStack->setCurrentIndex(index);
474 ui.storageSettingsStack->setCurrentIndex(index);
475 ui.storageSettingsStack->setVisible(!_backendFields[index].empty());
481 SyncPage::SyncPage(QWidget *parent) : QWizardPage(parent)
484 setTitle(tr("Storing Your Settings"));
485 setSubTitle(tr("Your settings are now being stored in the core, and you will be logged in automatically."));
489 void SyncPage::initializePage()
493 emit completeChanged();
495 // Fill in sync info about the storage layer.
496 StorageSelectionPage *storagePage = qobject_cast<StorageSelectionPage *>(wizard()->page(CoreConfigWizard::StorageSelectionPage));
497 QString backend = storagePage->backend();
498 QVariantMap backendProperties = storagePage->backendProperties();
499 ui.backend->setText(storagePage->displayName());
501 // Fill in sync info about the authentication layer.
502 AuthenticationSelectionPage *authPage = qobject_cast<AuthenticationSelectionPage *>(wizard()->page(CoreConfigWizard::AuthenticationSelectionPage));
503 QString authenticator = authPage->authenticator();
504 QVariantMap authProperties = authPage->authProperties();
505 ui.authenticator->setText(authPage->displayName());
507 ui.user->setText(wizard()->field("adminUser.user").toString());
509 emit setupCore(backend, backendProperties, authenticator, authProperties);
513 int SyncPage::nextId() const
517 return CoreConfigWizard::SyncRelayPage;
521 bool SyncPage::isComplete() const
523 return _complete || _hasError;
527 void SyncPage::setStatus(const QString &status)
529 ui.status->setText(status);
533 void SyncPage::setError(bool e)
537 emit completeChanged();
541 void SyncPage::setComplete(bool c)
548 /*** Sync Relay Page ***/
550 SyncRelayPage::SyncRelayPage(QWidget *parent) : QWizardPage(parent)
556 void SyncRelayPage::setMode(Mode m)
561 int SyncRelayPage::nextId() const
566 }; /* namespace CoreConfigWizardPages */