1 /***************************************************************************
2 * Copyright (C) 2005-2016 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 ***************************************************************************/
22 #include <QAbstractButton>
23 #include <QFormLayout>
27 #include "coreconfigwizard.h"
28 #include "coreconnection.h"
34 template<typename FieldInfo>
35 void createFieldWidgets(QGroupBox *fieldBox, const std::vector<FieldInfo> &fieldInfos)
37 // Create a config UI based on the field types sent from the backend
38 // We make some assumptions here (like integer range and password field names) that may not
39 // hold true for future authenticator types - but the only way around it for now would be to
40 // provide specialized config widgets for those (which may be a good idea anyway, e.g. if we
41 // think about client-side translations...)
43 QFormLayout *formLayout = new QFormLayout;
44 for (auto &&fieldInfo : fieldInfos) {
45 QWidget *widget {nullptr};
46 switch (std::get<2>(fieldInfo).type()) {
48 widget = new QSpinBox(fieldBox);
49 // Here we assume that int fields are always in 16 bit range, like ports
50 static_cast<QSpinBox *>(widget)->setMinimum(0);
51 static_cast<QSpinBox *>(widget)->setMaximum(65535);
52 static_cast<QSpinBox *>(widget)->setValue(std::get<2>(fieldInfo).toInt());
54 case QVariant::String:
55 widget = new QLineEdit(std::get<2>(fieldInfo).toString(), fieldBox);
56 // Here we assume that fields named something with "password" are actual password inputs
57 if (std::get<0>(fieldInfo).toLower().contains("password"))
58 static_cast<QLineEdit *>(widget)->setEchoMode(QLineEdit::Password);
61 qWarning() << "Unsupported type for backend property" << std::get<0>(fieldInfo);
64 widget->setObjectName(std::get<0>(fieldInfo));
65 formLayout->addRow(std::get<1>(fieldInfo) + ":", widget);
68 fieldBox->setLayout(formLayout);
72 template<typename FieldInfo>
73 QVariantMap propertiesFromFieldWidgets(QGroupBox *fieldBox, const std::vector<FieldInfo> &fieldInfos)
75 QVariantMap properties;
79 for (auto &&fieldInfo : fieldInfos) {
80 QString key = std::get<0>(fieldInfo);
82 switch (std::get<2>(fieldInfo).type()) {
84 QSpinBox *spinBox = fieldBox->findChild<QSpinBox *>(key);
86 value = spinBox->value();
88 qWarning() << "Could not find child widget for field" << key;
91 case QVariant::String: {
92 QLineEdit *lineEdit = fieldBox->findChild<QLineEdit *>(key);
94 value = lineEdit->text();
96 qWarning() << "Could not find child widget for field" << key;
100 qWarning() << "Unsupported type for backend property" << key;
102 properties[key] = std::move(value);
110 CoreConfigWizard::CoreConfigWizard(CoreConnection *connection, const QVariantList &backendInfos, const QVariantList &authInfos, QWidget *parent)
112 _connection{connection}
115 setAttribute(Qt::WA_DeleteOnClose);
117 setPage(IntroPage, new CoreConfigWizardPages::IntroPage(this));
118 setPage(AdminUserPage, new CoreConfigWizardPages::AdminUserPage(this));
119 setPage(AuthenticationSelectionPage, new CoreConfigWizardPages::AuthenticationSelectionPage(authInfos, this));
120 setPage(StorageSelectionPage, new CoreConfigWizardPages::StorageSelectionPage(backendInfos, this));
121 syncPage = new CoreConfigWizardPages::SyncPage(this);
122 connect(syncPage, SIGNAL(setupCore(const QString &, const QVariantMap &, const QString &, const QVariantMap &)),
123 SLOT(prepareCoreSetup(const QString &, const QVariantMap &, const QString &, const QVariantMap &)));
124 setPage(SyncPage, syncPage);
125 syncRelayPage = new CoreConfigWizardPages::SyncRelayPage(this);
126 connect(syncRelayPage, SIGNAL(startOver()), this, SLOT(startOver()));
127 setPage(SyncRelayPage, syncRelayPage);
128 //setPage(Page_StorageDetails, new StorageDetailsPage());
129 //setPage(Page_Conclusion, new ConclusionPage(storageProviders));
131 setStartId(IntroPage);
132 //setStartId(StorageSelectionPage);
135 setWizardStyle(ModernStyle);
138 setOption(HaveHelpButton, false);
139 setOption(NoBackButtonOnStartPage, true);
140 setOption(HaveNextButtonOnLastPage, false);
141 setOption(HaveFinishButtonOnEarlyPages, false);
142 setOption(NoCancelButton, true);
143 setOption(IndependentPages, true);
144 //setOption(ExtendedWatermarkPixmap, true);
148 setWindowTitle(tr("Core Configuration Wizard"));
149 setPixmap(QWizard::LogoPixmap, QIcon::fromTheme("quassel", QIcon(":/icons/quassel.png")).pixmap(48));
151 connect(connection, SIGNAL(coreSetupSuccess()), SLOT(coreSetupSuccess()));
152 connect(connection, SIGNAL(coreSetupFailed(QString)), SLOT(coreSetupFailed(QString)));
153 //connect(connection, SIGNAL(loginSuccess()), SLOT(loginSuccess()));
154 connect(connection, SIGNAL(synchronized()), SLOT(syncFinished()));
155 connect(this, SIGNAL(rejected()), connection, SLOT(disconnectFromCore()));
159 void CoreConfigWizard::prepareCoreSetup(const QString &backend, const QVariantMap &properties, const QString &authenticator, const QVariantMap &authProperties)
161 // Prevent the user from changing any settings he already specified...
162 foreach(int idx, visitedPages())
163 page(idx)->setEnabled(false);
165 // FIXME? We need to be able to set up older cores that don't have auth backend support.
166 // So if the core doesn't support that feature, don't pass those parameters.
167 if (!(Client::coreFeatures() & Quassel::Authenticators)) {
168 coreConnection()->setupCore(Protocol::SetupData(field("adminUser.user").toString(), field("adminUser.password").toString(), backend, properties));
171 coreConnection()->setupCore(Protocol::SetupData(field("adminUser.user").toString(), field("adminUser.password").toString(), backend, properties, authenticator, authProperties));
176 void CoreConfigWizard::coreSetupSuccess()
178 syncPage->setStatus(tr("Your core has been successfully configured. Logging you in..."));
179 syncPage->setError(false);
180 syncRelayPage->setMode(CoreConfigWizardPages::SyncRelayPage::Success);
181 coreConnection()->loginToCore(field("adminUser.user").toString(), field("adminUser.password").toString(), field("adminUser.rememberPasswd").toBool());
185 void CoreConfigWizard::coreSetupFailed(const QString &error)
187 syncPage->setStatus(tr("Core configuration failed:<br><b>%1</b><br>Press <em>Next</em> to start over.").arg(error));
188 syncPage->setError(true);
189 syncRelayPage->setMode(CoreConfigWizardPages::SyncRelayPage::Error);
190 //foreach(int idx, visitedPages()) page(idx)->setEnabled(true);
191 //setStartId(SyncPage);
196 void CoreConfigWizard::startOver()
198 foreach(int idx, visitedPages()) page(idx)->setEnabled(true);
199 setStartId(CoreConfigWizard::AdminUserPage);
204 void CoreConfigWizard::loginSuccess()
206 syncPage->setStatus(tr("Your are now logged into your freshly configured Quassel Core!<br>"
207 "Please remember to configure your identities and networks now."));
208 syncPage->setComplete(true);
209 syncPage->setFinalPage(true);
213 void CoreConfigWizard::syncFinished()
219 namespace CoreConfigWizardPages {
222 IntroPage::IntroPage(QWidget *parent) : QWizardPage(parent)
225 setTitle(tr("Introduction"));
226 //setSubTitle(tr("foobar"));
227 //setPixmap(QWizard::WatermarkPixmap, QPixmap(":icons/quassel-icon.png"));
231 int IntroPage::nextId() const
233 return CoreConfigWizard::AdminUserPage;
237 /*** Admin User Page ***/
239 AdminUserPage::AdminUserPage(QWidget *parent) : QWizardPage(parent)
242 setTitle(tr("Create Admin User"));
243 setSubTitle(tr("First, we will create a user on the core. This first user will have administrator privileges."));
245 registerField("adminUser.user*", ui.user);
246 registerField("adminUser.password*", ui.password);
247 registerField("adminUser.password2*", ui.password2);
248 registerField("adminUser.rememberPasswd", ui.rememberPasswd);
252 int AdminUserPage::nextId() const
254 // If the core doesn't support auth backends, skip that page!
255 if (!(Client::coreFeatures() & Quassel::Authenticators)) {
256 return CoreConfigWizard::StorageSelectionPage;
259 return CoreConfigWizard::AuthenticationSelectionPage;
264 bool AdminUserPage::isComplete() const
266 bool ok = !ui.user->text().isEmpty() && !ui.password->text().isEmpty() && ui.password->text() == ui.password2->text();
270 /*** Authentication Selection Page ***/
272 AuthenticationSelectionPage::AuthenticationSelectionPage(const QVariantList &authInfos, QWidget *parent)
273 : QWizardPage(parent)
277 setTitle(tr("Select Authentication Backend"));
278 setSubTitle(tr("Please select a backend for Quassel Core to use for authenticating users."));
280 registerField("authentication.backend", ui.backendList);
282 for (auto &&authInfo : authInfos) {
283 auto props = authInfo.toMap();
284 // Extract field infos to avoid having to reparse the list
285 std::vector<FieldInfo> fields;
286 const auto &list = props["SetupData"].toList();
287 for (int i = 0; i + 2 < list.size(); i += 3) {
288 fields.emplace_back(std::make_tuple(list[i].toString(), list[i+1].toString(), list[i+2]));
290 props.remove("SetupData");
292 _authProperties.emplace_back(props);
293 _authFields.emplace_back(std::move(fields));
295 // Create entry in authenticator selector
296 ui.backendList->addItem(props["DisplayName"].toString(), props["BackendId"].toString());
299 ui.backendList->setCurrentIndex(0);
303 int AuthenticationSelectionPage::nextId() const
305 return CoreConfigWizard::StorageSelectionPage;
309 QString AuthenticationSelectionPage::displayName() const
311 return ui.backendList->currentText();
315 QString AuthenticationSelectionPage::authenticator() const
317 #if QT_VERSION >= 0x050200
318 return ui.backendList->currentData().toString();
320 return ui.backendList->itemData(ui.backendList->currentIndex()).toString();
325 QVariantMap AuthenticationSelectionPage::authProperties() const
327 return propertiesFromFieldWidgets(_fieldBox, _authFields[ui.backendList->currentIndex()]);
331 void AuthenticationSelectionPage::on_backendList_currentIndexChanged(int index)
333 ui.description->setText(_authProperties[index]["Description"].toString());
336 layout()->removeWidget(_fieldBox);
337 _fieldBox->deleteLater();
340 if (!_authFields[index].empty()) {
341 _fieldBox = new QGroupBox(this);
342 _fieldBox->setTitle(tr("Authentication Settings"));
343 createFieldWidgets(_fieldBox, _authFields[index]);
344 static_cast<QVBoxLayout *>(layout())->insertWidget(layout()->indexOf(ui.descriptionBox) + 1, _fieldBox);
348 /*** Storage Selection Page ***/
350 StorageSelectionPage::StorageSelectionPage(const QVariantList &backendInfos, QWidget *parent)
351 : QWizardPage(parent)
355 setTitle(tr("Select Storage Backend"));
356 setSubTitle(tr("Please select a storage backend for Quassel Core."));
359 registerField("storage.backend", ui.backendList);
361 int defaultIndex {0}; // Legacy cores send backend infos in arbitrary order
363 for (auto &&backendInfo : backendInfos) {
364 auto props = backendInfo.toMap();
365 // Extract field infos to avoid having to reparse the list
366 std::vector<FieldInfo> fields;
368 // Legacy cores (prior to 0.13) didn't send SetupData for storage backends; deal with this
369 if (!props.contains("SetupData")) {
370 const auto &defaultValues = props["SetupDefaults"].toMap();
371 for (auto &&key : props["SetupKeys"].toStringList()) {
372 fields.emplace_back(std::make_tuple(key, key, defaultValues.value(key, QString{})));
374 if (props.value("IsDefault", false).toBool()) {
375 defaultIndex = ui.backendList->count();
379 const auto &list = props["SetupData"].toList();
380 for (int i = 0; i + 2 < list.size(); i += 3) {
381 fields.emplace_back(std::make_tuple(list[i].toString(), list[i+1].toString(), list[i+2]));
383 props.remove("SetupData");
385 props.remove("SetupKeys");
386 props.remove("SetupDefaults");
387 // Legacy cores (prior to 0.13) don't send the BackendId property
388 if (!props.contains("BackendId"))
389 props["BackendId"] = props["DisplayName"];
390 _backendProperties.emplace_back(props);
391 _backendFields.emplace_back(std::move(fields));
393 // Create entry in backend selector
394 ui.backendList->addItem(props["DisplayName"].toString(), props["BackendId"].toString());
397 ui.backendList->setCurrentIndex(defaultIndex);
401 int StorageSelectionPage::nextId() const
403 return CoreConfigWizard::SyncPage;
407 QString StorageSelectionPage::displayName() const
409 return ui.backendList->currentText();
413 QString StorageSelectionPage::backend() const
415 #if QT_VERSION >= 0x050200
416 return ui.backendList->currentData().toString();
418 return ui.backendList->itemData(ui.backendList->currentIndex()).toString();
423 QVariantMap StorageSelectionPage::backendProperties() const
425 return propertiesFromFieldWidgets(_fieldBox, _backendFields[ui.backendList->currentIndex()]);
429 void StorageSelectionPage::on_backendList_currentIndexChanged(int index)
431 ui.description->setText(_backendProperties[index]["Description"].toString());
434 layout()->removeWidget(_fieldBox);
435 _fieldBox->deleteLater();
438 if (!_backendFields[index].empty()) {
439 _fieldBox = new QGroupBox(this);
440 _fieldBox->setTitle(tr("Storage Settings"));
441 createFieldWidgets(_fieldBox, _backendFields[index]);
442 static_cast<QVBoxLayout *>(layout())->insertWidget(layout()->indexOf(ui.descriptionBox) + 1, _fieldBox);
449 SyncPage::SyncPage(QWidget *parent) : QWizardPage(parent)
452 setTitle(tr("Storing Your Settings"));
453 setSubTitle(tr("Your settings are now being stored in the core, and you will be logged in automatically."));
457 void SyncPage::initializePage()
461 emit completeChanged();
463 // Fill in sync info about the storage layer.
464 StorageSelectionPage *storagePage = qobject_cast<StorageSelectionPage *>(wizard()->page(CoreConfigWizard::StorageSelectionPage));
465 QString backend = storagePage->backend();
466 QVariantMap backendProperties = storagePage->backendProperties();
467 ui.backend->setText(storagePage->displayName());
469 // Fill in sync info about the authentication layer.
470 AuthenticationSelectionPage *authPage = qobject_cast<AuthenticationSelectionPage *>(wizard()->page(CoreConfigWizard::AuthenticationSelectionPage));
471 QString authenticator = authPage->authenticator();
472 QVariantMap authProperties = authPage->authProperties();
473 ui.authenticator->setText(authPage->displayName());
475 ui.user->setText(wizard()->field("adminUser.user").toString());
477 emit setupCore(backend, backendProperties, authenticator, authProperties);
481 int SyncPage::nextId() const
485 return CoreConfigWizard::SyncRelayPage;
489 bool SyncPage::isComplete() const
491 return _complete || _hasError;
495 void SyncPage::setStatus(const QString &status)
497 ui.status->setText(status);
501 void SyncPage::setError(bool e)
505 emit completeChanged();
509 void SyncPage::setComplete(bool c)
516 /*** Sync Relay Page ***/
518 SyncRelayPage::SyncRelayPage(QWidget *parent) : QWizardPage(parent)
524 void SyncRelayPage::setMode(Mode m)
529 int SyncRelayPage::nextId() const
534 }; /* namespace CoreConfigWizardPages */