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"