File indexing completed on 2024-06-23 05:13:54

0001 /* -*- mode: c++; c-basic-offset:4 -*-
0002     crypto/gui/wizard.cpp
0003 
0004     This file is part of Kleopatra, the KDE keymanager
0005     SPDX-FileCopyrightText: 2007 Klarälvdalens Datakonsult AB
0006 
0007     SPDX-License-Identifier: GPL-2.0-or-later
0008 */
0009 
0010 #include <config-kleopatra.h>
0011 
0012 #include "wizard.h"
0013 #include "wizardpage.h"
0014 
0015 #include <utils/kleo_assert.h>
0016 
0017 #include <Libkleo/Algorithm>
0018 
0019 #include <KGuiItem>
0020 #include <KLocalizedString>
0021 #include <KStandardGuiItem>
0022 #include <QPushButton>
0023 
0024 #include "kleopatra_debug.h"
0025 #include <QDialogButtonBox>
0026 #include <QFrame>
0027 #include <QLabel>
0028 #include <QStackedWidget>
0029 #include <QTimer>
0030 #include <QVBoxLayout>
0031 
0032 #include <map>
0033 #include <set>
0034 
0035 using namespace Kleo::Crypto::Gui;
0036 
0037 class Wizard::Private
0038 {
0039     friend class ::Wizard;
0040     Wizard *const q;
0041 
0042 public:
0043     explicit Private(Wizard *qq);
0044     ~Private();
0045 
0046     void updateButtonStates();
0047     bool isLastPage(int id) const;
0048     int previousPage() const;
0049     void updateHeader();
0050 
0051 private:
0052     std::vector<int> pageOrder;
0053     std::set<int> hiddenPages;
0054     std::map<int, WizardPage *> idToPage;
0055     int currentId = -1;
0056     QStackedWidget *const stack;
0057     QPushButton *nextButton = nullptr;
0058     QPushButton *backButton = nullptr;
0059     QPushButton *cancelButton = nullptr;
0060     KGuiItem finishItem;
0061     KGuiItem nextItem;
0062     QFrame *titleFrame = nullptr;
0063     QLabel *titleLabel = nullptr;
0064     QLabel *subTitleLabel = nullptr;
0065     QFrame *explanationFrame = nullptr;
0066     QLabel *explanationLabel = nullptr;
0067     QTimer *nextPageTimer = nullptr;
0068 };
0069 
0070 Wizard::Private::Private(Wizard *qq)
0071     : q(qq)
0072     , stack(new QStackedWidget)
0073 {
0074     nextPageTimer = new QTimer(q);
0075     nextPageTimer->setInterval(0);
0076     connect(nextPageTimer, &QTimer::timeout, q, &Wizard::next);
0077     nextItem = KGuiItem(i18n("&Next"));
0078     finishItem = KStandardGuiItem::ok();
0079     auto const top = new QVBoxLayout(q);
0080     top->setContentsMargins(0, 0, 0, 0);
0081     titleFrame = new QFrame;
0082     titleFrame->setFrameShape(QFrame::StyledPanel);
0083     titleFrame->setAutoFillBackground(true);
0084     titleFrame->setBackgroundRole(QPalette::Base);
0085     auto const titleLayout = new QVBoxLayout(titleFrame);
0086     titleLabel = new QLabel;
0087     titleLayout->addWidget(titleLabel);
0088     subTitleLabel = new QLabel;
0089     subTitleLabel->setWordWrap(true);
0090     titleLayout->addWidget(subTitleLabel);
0091     top->addWidget(titleFrame);
0092     titleFrame->setVisible(false);
0093 
0094     top->addWidget(stack);
0095 
0096     explanationFrame = new QFrame;
0097     explanationFrame->setFrameShape(QFrame::StyledPanel);
0098     explanationFrame->setAutoFillBackground(true);
0099     explanationFrame->setBackgroundRole(QPalette::Base);
0100     auto const explanationLayout = new QVBoxLayout(explanationFrame);
0101     explanationLabel = new QLabel;
0102     explanationLabel->setWordWrap(true);
0103     explanationLayout->addWidget(explanationLabel);
0104     top->addWidget(explanationFrame);
0105     explanationFrame->setVisible(false);
0106 
0107     auto buttonWidget = new QWidget;
0108     auto buttonLayout = new QHBoxLayout(buttonWidget);
0109     auto const box = new QDialogButtonBox;
0110 
0111     cancelButton = box->addButton(QDialogButtonBox::Cancel);
0112     q->connect(cancelButton, &QPushButton::clicked, q, &Wizard::reject);
0113 
0114     backButton = new QPushButton;
0115     backButton->setText(i18n("Back"));
0116     q->connect(backButton, &QPushButton::clicked, q, &Wizard::back);
0117     box->addButton(backButton, QDialogButtonBox::ActionRole);
0118 
0119     nextButton = new QPushButton;
0120     KGuiItem::assign(nextButton, nextItem);
0121     q->connect(nextButton, &QPushButton::clicked, q, &Wizard::next);
0122     box->addButton(nextButton, QDialogButtonBox::ActionRole);
0123     buttonLayout->addWidget(box);
0124 
0125     top->addWidget(buttonWidget);
0126 
0127     q->connect(q, &Wizard::rejected, q, &Wizard::canceled);
0128 }
0129 
0130 Wizard::Private::~Private()
0131 {
0132     qCDebug(KLEOPATRA_LOG) << q << __func__;
0133 }
0134 
0135 bool Wizard::Private::isLastPage(int id) const
0136 {
0137     return !pageOrder.empty() ? pageOrder.back() == id : false;
0138 }
0139 
0140 void Wizard::Private::updateButtonStates()
0141 {
0142     const bool isLast = isLastPage(currentId);
0143     const bool canGoToNext = q->canGoToNextPage();
0144     WizardPage *const page = q->page(currentId);
0145     const KGuiItem customNext = page ? page->customNextButton() : KGuiItem();
0146     if (customNext.text().isEmpty() && customNext.icon().isNull()) {
0147         KGuiItem::assign(nextButton, isLast ? finishItem : nextItem);
0148     } else {
0149         KGuiItem::assign(nextButton, customNext);
0150     }
0151     nextButton->setEnabled(canGoToNext);
0152     cancelButton->setEnabled(!isLast || !canGoToNext);
0153     backButton->setEnabled(q->canGoToPreviousPage());
0154     if (page && page->autoAdvance() && page->isComplete()) {
0155         nextPageTimer->start();
0156     }
0157 }
0158 
0159 void Wizard::Private::updateHeader()
0160 {
0161     WizardPage *const widget = q->page(currentId);
0162     Q_ASSERT(!widget || stack->indexOf(widget) != -1);
0163     if (widget) {
0164         stack->setCurrentWidget(widget);
0165     }
0166     const QString title = widget ? widget->title() : QString();
0167     const QString subTitle = widget ? widget->subTitle() : QString();
0168     const QString explanation = widget ? widget->explanation() : QString();
0169     titleFrame->setVisible(!title.isEmpty() || !subTitle.isEmpty() || !explanation.isEmpty());
0170     titleLabel->setVisible(!title.isEmpty());
0171     titleLabel->setText(title);
0172     subTitleLabel->setText(subTitle);
0173     subTitleLabel->setVisible(!subTitle.isEmpty());
0174     explanationFrame->setVisible(!explanation.isEmpty());
0175     explanationLabel->setVisible(!explanation.isEmpty());
0176     explanationLabel->setText(explanation);
0177     q->resize(q->sizeHint().expandedTo(q->size()));
0178 }
0179 
0180 Wizard::Wizard(QWidget *parent, Qt::WindowFlags f)
0181     : QDialog(parent, f)
0182     , d(new Private(this))
0183 {
0184 }
0185 
0186 Wizard::~Wizard()
0187 {
0188     qCDebug(KLEOPATRA_LOG) << this << __func__;
0189 }
0190 
0191 void Wizard::setPage(int id, WizardPage *widget)
0192 {
0193     kleo_assert(id != InvalidPage);
0194     kleo_assert(d->idToPage.find(id) == d->idToPage.end());
0195     d->idToPage[id] = widget;
0196     d->stack->addWidget(widget);
0197     connect(widget, SIGNAL(completeChanged()), this, SLOT(updateButtonStates()));
0198     connect(widget, SIGNAL(titleChanged()), this, SLOT(updateHeader()));
0199     connect(widget, SIGNAL(subTitleChanged()), this, SLOT(updateHeader()));
0200     connect(widget, SIGNAL(explanationChanged()), this, SLOT(updateHeader()));
0201     connect(widget, SIGNAL(autoAdvanceChanged()), this, SLOT(updateButtonStates()));
0202     connect(widget, SIGNAL(windowTitleChanged(QString)), this, SLOT(setWindowTitle(QString)));
0203 }
0204 
0205 void Wizard::setPageOrder(const std::vector<int> &pageOrder)
0206 {
0207     d->pageOrder = pageOrder;
0208     d->hiddenPages.clear();
0209     if (pageOrder.empty()) {
0210         return;
0211     }
0212     setCurrentPage(pageOrder.front());
0213 }
0214 
0215 void Wizard::setCurrentPage(int id)
0216 {
0217     d->currentId = id;
0218     if (id == InvalidPage) {
0219         return;
0220     }
0221     d->updateHeader();
0222     d->updateButtonStates();
0223 }
0224 
0225 void Wizard::setPageVisible(int id, bool visible)
0226 {
0227     if (visible) {
0228         d->hiddenPages.erase(id);
0229     } else {
0230         d->hiddenPages.insert(id);
0231     }
0232     if (currentPage() == id && !visible) {
0233         next();
0234     }
0235 }
0236 
0237 int Wizard::currentPage() const
0238 {
0239     return d->currentId;
0240 }
0241 
0242 bool Wizard::canGoToNextPage() const
0243 {
0244     const WizardPage *const current = currentPageWidget();
0245     return current ? current->isComplete() : false;
0246 }
0247 
0248 bool Wizard::canGoToPreviousPage() const
0249 {
0250     const int prev = d->previousPage();
0251     if (prev == InvalidPage) {
0252         return false;
0253     }
0254     const WizardPage *const prevPage = page(prev);
0255     Q_ASSERT(prevPage);
0256     return !prevPage->isCommitPage();
0257 }
0258 
0259 void Wizard::next()
0260 {
0261     WizardPage *const current = currentPageWidget();
0262     if (current) {
0263         current->onNext();
0264     }
0265     onNext(d->currentId);
0266     auto it = Kleo::binary_find(d->pageOrder.begin(), d->pageOrder.end(), d->currentId);
0267     Q_ASSERT(it != d->pageOrder.end());
0268 
0269     do {
0270         ++it;
0271     } while (d->hiddenPages.find(*it) != d->hiddenPages.end());
0272 
0273     if (it == d->pageOrder.end()) { // "Finish"
0274         d->currentId = InvalidPage;
0275         close();
0276     } else { // "next"
0277         setCurrentPage(*it);
0278     }
0279 }
0280 
0281 int Wizard::Private::previousPage() const
0282 {
0283     if (pageOrder.empty()) {
0284         return InvalidPage;
0285     }
0286 
0287     auto it = Kleo::binary_find(pageOrder.begin(), pageOrder.end(), currentId);
0288     if (it == pageOrder.begin() || it == pageOrder.end()) {
0289         return InvalidPage;
0290     }
0291 
0292     do {
0293         --it;
0294     } while (it != pageOrder.begin() && hiddenPages.find(*it) != hiddenPages.end());
0295     return *it;
0296 }
0297 
0298 void Wizard::back()
0299 {
0300     onBack(d->currentId);
0301     const int prev = d->previousPage();
0302     if (prev == InvalidPage) {
0303         return;
0304     }
0305     setCurrentPage(prev);
0306 }
0307 
0308 const WizardPage *Wizard::page(int id) const
0309 {
0310     if (id == InvalidPage) {
0311         return nullptr;
0312     }
0313 
0314     const auto it = d->idToPage.find(id);
0315     kleo_assert(it != d->idToPage.end());
0316     return (*it).second;
0317 }
0318 
0319 const WizardPage *Wizard::currentPageWidget() const
0320 {
0321     return page(d->currentId);
0322 }
0323 
0324 WizardPage *Wizard::currentPageWidget()
0325 {
0326     return page(d->currentId);
0327 }
0328 
0329 void Wizard::onNext(int currentId)
0330 {
0331     Q_UNUSED(currentId)
0332 }
0333 
0334 void Wizard::onBack(int currentId)
0335 {
0336     Q_UNUSED(currentId)
0337 }
0338 
0339 WizardPage *Wizard::page(int id)
0340 {
0341     if (id == InvalidPage) {
0342         return nullptr;
0343     }
0344 
0345     const auto it = d->idToPage.find(id);
0346     kleo_assert(it != d->idToPage.end());
0347     return (*it).second;
0348 }
0349 
0350 #include "moc_wizard.cpp"