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"
23 #include <QAbstractButton>
24 #include <QCoreApplication>
26 #include <QFormLayout>
30 #include "coreconnection.h"
36 QGroupBox* createDescriptionBox(const QString& description)
38 auto box = new QGroupBox;
39 auto layout = new QVBoxLayout(box);
40 auto label = new QLabel(description, box);
41 label->setWordWrap(true);
42 layout->addWidget(label);
43 layout->setAlignment(label, Qt::AlignTop);
44 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);
89 template<typename FieldInfo>
90 QVariantMap propertiesFromFieldWidgets(QGroupBox* fieldBox, const std::vector<FieldInfo>& fieldInfos)
92 QVariantMap properties;
96 for (auto&& fieldInfo : fieldInfos) {
97 QString key = std::get<0>(fieldInfo);
99 switch (std::get<2>(fieldInfo).type()) {
100 case QVariant::Int: {
101 auto* spinBox = fieldBox->findChild<QSpinBox*>(key);
103 value = spinBox->value();
105 qWarning() << "Could not find child widget for field" << key;
108 case QVariant::String: {
109 auto* lineEdit = fieldBox->findChild<QLineEdit*>(key);
111 value = lineEdit->text();
113 qWarning() << "Could not find child widget for field" << key;
117 qWarning() << "Unsupported type for backend property" << key;
119 properties[key] = std::move(value);
126 CoreConfigWizard::CoreConfigWizard(CoreConnection* connection, const QVariantList& backendInfos, const QVariantList& authInfos, QWidget* parent)
128 , _connection{connection}
131 setAttribute(Qt::WA_DeleteOnClose);
133 setPage(IntroPage, new CoreConfigWizardPages::IntroPage(this));
134 setPage(AdminUserPage, new CoreConfigWizardPages::AdminUserPage(this));
135 setPage(AuthenticationSelectionPage, new CoreConfigWizardPages::AuthenticationSelectionPage(authInfos, this));
136 setPage(StorageSelectionPage, new CoreConfigWizardPages::StorageSelectionPage(backendInfos, this));
137 syncPage = new CoreConfigWizardPages::SyncPage(this);
138 connect(syncPage, &CoreConfigWizardPages::SyncPage::setupCore, this, &CoreConfigWizard::prepareCoreSetup);
139 setPage(SyncPage, syncPage);
140 syncRelayPage = new CoreConfigWizardPages::SyncRelayPage(this);
141 connect(syncRelayPage, &CoreConfigWizardPages::SyncRelayPage::startOver, this, &CoreConfigWizard::startOver);
142 setPage(SyncRelayPage, syncRelayPage);
144 setStartId(IntroPage);
147 setWizardStyle(ModernStyle);
150 setOption(HaveHelpButton, false);
151 setOption(NoBackButtonOnStartPage, true);
152 setOption(HaveNextButtonOnLastPage, false);
153 setOption(HaveFinishButtonOnEarlyPages, false);
154 setOption(NoCancelButton, true);
155 setOption(IndependentPages, true);
159 setWindowTitle(CoreConfigWizard::tr("Core Configuration Wizard"));
160 setPixmap(QWizard::LogoPixmap, icon::get("quassel").pixmap(48));
162 connect(connection, &CoreConnection::coreSetupSuccess, this, &CoreConfigWizard::coreSetupSuccess);
163 connect(connection, &CoreConnection::coreSetupFailed, this, &CoreConfigWizard::coreSetupFailed);
164 connect(connection, &CoreConnection::synchronized, this, &CoreConfigWizard::syncFinished);
165 connect(this, &QDialog::rejected, connection, selectOverload<>(&CoreConnection::disconnectFromCore));
167 // Resize all pages to the size hint of the largest one, so the wizard is large enough
169 for (int id : pageIds()) {
172 maxSize = maxSize.expandedTo(p->sizeHint());
174 for (int id : pageIds()) {
175 page(id)->setFixedSize(maxSize);
179 void CoreConfigWizard::prepareCoreSetup(const QString& backend,
180 const QVariantMap& properties,
181 const QString& authenticator,
182 const QVariantMap& authProperties)
184 // Prevent the user from changing any settings he already specified...
185 for (auto&& idx : visitedPages())
186 page(idx)->setEnabled(false);
188 // FIXME? We need to be able to set up older cores that don't have auth backend support.
189 // So if the core doesn't support that feature, don't pass those parameters.
190 if (!Client::isCoreFeatureEnabled(Quassel::Feature::Authenticators)) {
191 coreConnection()->setupCore(
192 Protocol::SetupData(field("adminUser.user").toString(), field("adminUser.password").toString(), backend, properties));
195 coreConnection()->setupCore(Protocol::SetupData(field("adminUser.user").toString(),
196 field("adminUser.password").toString(),
204 void CoreConfigWizard::coreSetupSuccess()
206 syncPage->setStatus(tr("Your core has been successfully configured. Logging you in..."));
207 syncPage->setError(false);
208 syncRelayPage->setMode(CoreConfigWizardPages::SyncRelayPage::Success);
209 coreConnection()->loginToCore(field("adminUser.user").toString(),
210 field("adminUser.password").toString(),
211 field("adminUser.rememberPasswd").toBool());
214 void CoreConfigWizard::coreSetupFailed(const QString& error)
216 syncPage->setStatus(tr("Core configuration failed:<br><b>%1</b><br>Press <em>Next</em> to start over.").arg(error));
217 syncPage->setError(true);
218 syncRelayPage->setMode(CoreConfigWizardPages::SyncRelayPage::Error);
219 // foreach(int idx, visitedPages()) page(idx)->setEnabled(true);
220 // setStartId(SyncPage);
224 void CoreConfigWizard::startOver()
226 foreach (int idx, visitedPages())
227 page(idx)->setEnabled(true);
228 setStartId(CoreConfigWizard::AdminUserPage);
232 void CoreConfigWizard::syncFinished()
237 namespace CoreConfigWizardPages {
240 IntroPage::IntroPage(QWidget* parent)
241 : QWizardPage(parent)
244 setTitle(tr("Introduction"));
245 // setSubTitle(tr("foobar"));
246 // setPixmap(QWizard::WatermarkPixmap, QPixmap(":icons/quassel-icon.png"));
249 int IntroPage::nextId() const
251 return CoreConfigWizard::AdminUserPage;
254 /*** Admin User Page ***/
256 AdminUserPage::AdminUserPage(QWidget* parent)
257 : QWizardPage(parent)
260 setTitle(tr("Create Admin User"));
261 setSubTitle(tr("First, we will create a user on the core. This first user will have administrator privileges."));
263 registerField("adminUser.user*", ui.user);
264 registerField("adminUser.password*", ui.password);
265 registerField("adminUser.password2*", ui.password2);
266 registerField("adminUser.rememberPasswd", ui.rememberPasswd);
269 int AdminUserPage::nextId() const
271 // If the core doesn't support auth backends, skip that page!
272 if (!Client::isCoreFeatureEnabled(Quassel::Feature::Authenticators)) {
273 return CoreConfigWizard::StorageSelectionPage;
276 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 sp.setRetainSizeWhenHidden(true);
322 ui.descriptionStack->setSizePolicy(sp);
323 ui.authSettingsStack->setSizePolicy(sp);
325 ui.descriptionStack->adjustSize();
326 ui.authSettingsStack->adjustSize();
328 ui.backendList->setCurrentIndex(0);
331 int AuthenticationSelectionPage::nextId() const
333 return CoreConfigWizard::StorageSelectionPage;
336 QString AuthenticationSelectionPage::displayName() const
338 return ui.backendList->currentText();
341 QString AuthenticationSelectionPage::authenticator() const
343 return ui.backendList->currentData().toString();
346 QVariantMap AuthenticationSelectionPage::authProperties() const
348 return propertiesFromFieldWidgets(qobject_cast<QGroupBox*>(ui.authSettingsStack->currentWidget()),
349 _authFields[ui.backendList->currentIndex()]);
352 void AuthenticationSelectionPage::on_backendList_currentIndexChanged(int index)
354 ui.descriptionStack->setCurrentIndex(index);
355 ui.authSettingsStack->setCurrentIndex(index);
356 ui.authSettingsStack->setVisible(!_authFields[index].empty());
359 /*** Storage Selection Page ***/
361 StorageSelectionPage::StorageSelectionPage(const QVariantList& backendInfos, QWidget* parent)
362 : QWizardPage(parent)
366 setTitle(tr("Select Storage Backend"));
367 setSubTitle(tr("Please select a storage backend for Quassel Core."));
370 registerField("storage.backend", ui.backendList);
372 int defaultIndex{0}; // Legacy cores send backend infos in arbitrary order
374 for (auto&& backendInfo : backendInfos) {
375 auto props = backendInfo.toMap();
376 // Extract field infos to avoid having to reparse the list
377 std::vector<FieldInfo> fields;
379 // Legacy cores (prior to 0.13) didn't send SetupData for storage backends; deal with this
380 if (!props.contains("SetupData")) {
381 const auto& defaultValues = props["SetupDefaults"].toMap();
382 for (auto&& key : props["SetupKeys"].toStringList()) {
383 fields.emplace_back(std::make_tuple(key, key, defaultValues.value(key, QString{})));
385 if (props.value("IsDefault", false).toBool()) {
386 defaultIndex = ui.backendList->count();
390 const auto& list = props["SetupData"].toList();
391 for (int i = 0; i + 2 < list.size(); i += 3) {
392 fields.emplace_back(std::make_tuple(list[i].toString(), list[i + 1].toString(), list[i + 2]));
394 props.remove("SetupData");
396 props.remove("SetupKeys");
397 props.remove("SetupDefaults");
398 // Legacy cores (prior to 0.13) don't send the BackendId property
399 if (!props.contains("BackendId"))
400 props["BackendId"] = props["DisplayName"];
401 _backendProperties.emplace_back(std::move(props));
402 _backendFields.emplace_back(std::move(fields));
405 ui.backendList->addItem(_backendProperties.back()["DisplayName"].toString(), _backendProperties.back()["BackendId"].toString());
406 ui.descriptionStack->addWidget(createDescriptionBox(_backendProperties.back()["Description"].toString()));
407 ui.storageSettingsStack->addWidget(createFieldBox(tr("Storage Settings"), _backendFields.back()));
410 // Do some trickery to make the page large enough
411 setSizePolicy({QSizePolicy::Fixed, QSizePolicy::Fixed});
413 QSizePolicy sp{QSizePolicy::MinimumExpanding, QSizePolicy::Fixed};
414 sp.setRetainSizeWhenHidden(true);
415 ui.descriptionStack->setSizePolicy(sp);
416 ui.storageSettingsStack->setSizePolicy(sp);
418 ui.descriptionStack->adjustSize();
419 ui.storageSettingsStack->adjustSize();
421 ui.backendList->setCurrentIndex(defaultIndex);
424 int StorageSelectionPage::nextId() const
426 return CoreConfigWizard::SyncPage;
429 QString StorageSelectionPage::displayName() const
431 return ui.backendList->currentText();
434 QString StorageSelectionPage::backend() const
436 return ui.backendList->currentData().toString();
439 QVariantMap StorageSelectionPage::backendProperties() const
441 return propertiesFromFieldWidgets(qobject_cast<QGroupBox*>(ui.storageSettingsStack->currentWidget()),
442 _backendFields[ui.backendList->currentIndex()]);
445 void StorageSelectionPage::on_backendList_currentIndexChanged(int index)
447 ui.descriptionStack->setCurrentIndex(index);
448 ui.storageSettingsStack->setCurrentIndex(index);
449 ui.storageSettingsStack->setVisible(!_backendFields[index].empty());
454 SyncPage::SyncPage(QWidget* parent)
455 : QWizardPage(parent)
458 setTitle(tr("Storing Your Settings"));
459 setSubTitle(tr("Your settings are now being stored in the core, and you will be logged in automatically."));
462 void SyncPage::initializePage()
466 emit completeChanged();
468 // Fill in sync info about the storage layer.
469 auto* storagePage = qobject_cast<StorageSelectionPage*>(wizard()->page(CoreConfigWizard::StorageSelectionPage));
470 QString backend = storagePage->backend();
471 QVariantMap backendProperties = storagePage->backendProperties();
472 ui.backend->setText(storagePage->displayName());
474 // Fill in sync info about the authentication layer.
475 auto* authPage = qobject_cast<AuthenticationSelectionPage*>(wizard()->page(CoreConfigWizard::AuthenticationSelectionPage));
476 QString authenticator = authPage->authenticator();
477 QVariantMap authProperties = authPage->authProperties();
478 ui.authenticator->setText(authPage->displayName());
480 ui.user->setText(wizard()->field("adminUser.user").toString());
482 emit setupCore(backend, backendProperties, authenticator, authProperties);
485 int SyncPage::nextId() const
489 return CoreConfigWizard::SyncRelayPage;
492 bool SyncPage::isComplete() const
494 return _complete || _hasError;
497 void SyncPage::setStatus(const QString& status)
499 ui.status->setText(status);
502 void SyncPage::setError(bool e)
506 emit completeChanged();
509 void SyncPage::setComplete(bool c)
515 /*** Sync Relay Page ***/
517 SyncRelayPage::SyncRelayPage(QWidget* parent)
518 : QWizardPage(parent)
523 void SyncRelayPage::setMode(Mode m)
528 int SyncRelayPage::nextId() const
533 } /* namespace CoreConfigWizardPages */