1 /* -*- mode: c++; c-basic-offset:4 -*-
2 crypto/gui/wizard.cpp
3
4 This file is part of Kleopatra, the KDE keymanager
5 SPDX-FileCopyrightText: 2007 Klarälvdalens Datakonsult AB
6
7 SPDX-License-Identifier: GPL-2.0-or-later
8 */
9
10 #include <config-kleopatra.h>
11
12 #include "wizard.h"
13 #include "wizardpage.h"
14
15 #include <utils/kleo_assert.h>
16
17 #include <Libkleo/Algorithm>
18
19 #include <KGuiItem>
20 #include <KLocalizedString>
21 #include <QPushButton>
22 #include <KStandardGuiItem>
23
24 #include <QDialogButtonBox>
25 #include <QFrame>
26 #include <QLabel>
27 #include <QStackedWidget>
28 #include <QTimer>
29 #include <QVBoxLayout>
30 #include "kleopatra_debug.h"
31
32 #include <map>
33 #include <set>
34
35
36 using namespace Kleo::Crypto::Gui;
37
38 class Wizard::Private
39 {
40 friend class ::Wizard;
41 Wizard *const q;
42 public:
43 explicit Private(Wizard *qq);
44 ~Private();
45
46 void updateButtonStates();
47 bool isLastPage(int id) const;
48 int previousPage() const;
49 void updateHeader();
50
51 private:
52 std::vector<int> pageOrder;
53 std::set<int> hiddenPages;
54 std::map<int, WizardPage *> idToPage;
55 int currentId = -1;
56 QStackedWidget *const stack;
57 QPushButton *nextButton = nullptr;
58 QPushButton *backButton = nullptr;
59 QPushButton *cancelButton = nullptr;
60 KGuiItem finishItem;
61 KGuiItem nextItem;
62 QFrame *titleFrame = nullptr;
63 QLabel *titleLabel = nullptr;
64 QLabel *subTitleLabel = nullptr;
65 QFrame *explanationFrame = nullptr;
66 QLabel *explanationLabel = nullptr;
67 QTimer *nextPageTimer = nullptr;
68 };
69
Private(Wizard * qq)70 Wizard::Private::Private(Wizard *qq)
71 : q(qq), stack(new QStackedWidget)
72 {
73 nextPageTimer = new QTimer(q);
74 nextPageTimer->setInterval(0);
75 connect(nextPageTimer, &QTimer::timeout, q, &Wizard::next);
76 nextItem = KGuiItem(i18n("&Next"));
77 finishItem = KStandardGuiItem::ok();
78 auto const top = new QVBoxLayout(q);
79 top->setContentsMargins(0, 0, 0, 0);
80 titleFrame = new QFrame;
81 titleFrame->setFrameShape(QFrame::StyledPanel);
82 titleFrame->setAutoFillBackground(true);
83 titleFrame->setBackgroundRole(QPalette::Base);
84 auto const titleLayout = new QVBoxLayout(titleFrame);
85 titleLabel = new QLabel;
86 titleLayout->addWidget(titleLabel);
87 subTitleLabel = new QLabel;
88 subTitleLabel->setWordWrap(true);
89 titleLayout->addWidget(subTitleLabel);
90 top->addWidget(titleFrame);
91 titleFrame->setVisible(false);
92
93 top->addWidget(stack);
94
95 explanationFrame = new QFrame;
96 explanationFrame->setFrameShape(QFrame::StyledPanel);
97 explanationFrame->setAutoFillBackground(true);
98 explanationFrame->setBackgroundRole(QPalette::Base);
99 auto const explanationLayout = new QVBoxLayout(explanationFrame);
100 explanationLabel = new QLabel;
101 explanationLabel->setWordWrap(true);
102 explanationLayout->addWidget(explanationLabel);
103 top->addWidget(explanationFrame);
104 explanationFrame->setVisible(false);
105
106 auto buttonWidget = new QWidget;
107 auto buttonLayout = new QHBoxLayout(buttonWidget);
108 auto const box = new QDialogButtonBox;
109
110 cancelButton = box->addButton(QDialogButtonBox::Cancel);
111 q->connect(cancelButton, &QPushButton::clicked, q, &Wizard::reject);
112
113 backButton = new QPushButton;
114 backButton->setText(i18n("Back"));
115 q->connect(backButton, &QPushButton::clicked, q, &Wizard::back);
116 box->addButton(backButton, QDialogButtonBox::ActionRole);
117
118 nextButton = new QPushButton;
119 KGuiItem::assign(nextButton, nextItem);
120 q->connect(nextButton, &QPushButton::clicked, q, &Wizard::next);
121 box->addButton(nextButton, QDialogButtonBox::ActionRole);
122 buttonLayout->addWidget(box);
123
124 top->addWidget(buttonWidget);
125
126 q->connect(q, &Wizard::rejected, q, &Wizard::canceled);
127 }
128
~Private()129 Wizard::Private::~Private()
130 {
131 qCDebug(KLEOPATRA_LOG);
132 }
133
isLastPage(int id) const134 bool Wizard::Private::isLastPage(int id) const
135 {
136 return !pageOrder.empty() ? pageOrder.back() == id : false;
137 }
138
updateButtonStates()139 void Wizard::Private::updateButtonStates()
140 {
141 const bool isLast = isLastPage(currentId);
142 const bool canGoToNext = q->canGoToNextPage();
143 WizardPage *const page = q->page(currentId);
144 const KGuiItem customNext = page ? page->customNextButton() : KGuiItem();
145 if (customNext.text().isEmpty() && customNext.icon().isNull()) {
146 KGuiItem::assign(nextButton, isLast ? finishItem : nextItem);
147 } else {
148 KGuiItem::assign(nextButton, customNext);
149 }
150 nextButton->setEnabled(canGoToNext);
151 cancelButton->setEnabled(!isLast || !canGoToNext);
152 backButton->setEnabled(q->canGoToPreviousPage());
153 if (page && page->autoAdvance() && page->isComplete()) {
154 nextPageTimer->start();
155 }
156 }
157
updateHeader()158 void Wizard::Private::updateHeader()
159 {
160 WizardPage *const widget = q->page(currentId);
161 Q_ASSERT(!widget || stack->indexOf(widget) != -1);
162 if (widget) {
163 stack->setCurrentWidget(widget);
164 }
165 const QString title = widget ? widget->title() : QString();
166 const QString subTitle = widget ? widget->subTitle() : QString();
167 const QString explanation = widget ? widget->explanation() : QString();
168 titleFrame->setVisible(!title.isEmpty() || !subTitle.isEmpty() || !explanation.isEmpty());
169 titleLabel->setVisible(!title.isEmpty());
170 titleLabel->setText(title);
171 subTitleLabel->setText(subTitle);
172 subTitleLabel->setVisible(!subTitle.isEmpty());
173 explanationFrame->setVisible(!explanation.isEmpty());
174 explanationLabel->setVisible(!explanation.isEmpty());
175 explanationLabel->setText(explanation);
176 q->resize(q->sizeHint().expandedTo(q->size()));
177 }
178
Wizard(QWidget * parent,Qt::WindowFlags f)179 Wizard::Wizard(QWidget *parent, Qt::WindowFlags f)
180 : QDialog(parent, f), d(new Private(this))
181 {
182
183 }
184
~Wizard()185 Wizard::~Wizard()
186 {
187 qCDebug(KLEOPATRA_LOG);
188 }
189
setPage(int id,WizardPage * widget)190 void Wizard::setPage(int id, WizardPage *widget)
191 {
192 kleo_assert(id != InvalidPage);
193 kleo_assert(d->idToPage.find(id) == d->idToPage.end());
194 d->idToPage[id] = widget;
195 d->stack->addWidget(widget);
196 connect(widget, SIGNAL(completeChanged()), this, SLOT(updateButtonStates()));
197 connect(widget, SIGNAL(titleChanged()), this, SLOT(updateHeader()));
198 connect(widget, SIGNAL(subTitleChanged()), this, SLOT(updateHeader()));
199 connect(widget, SIGNAL(explanationChanged()), this, SLOT(updateHeader()));
200 connect(widget, SIGNAL(autoAdvanceChanged()), this, SLOT(updateButtonStates()));
201 connect(widget, SIGNAL(windowTitleChanged(QString)), this, SLOT(setWindowTitle(QString)));
202 }
203
setPageOrder(const std::vector<int> & pageOrder)204 void Wizard::setPageOrder(const std::vector<int> &pageOrder)
205 {
206 d->pageOrder = pageOrder;
207 d->hiddenPages.clear();
208 if (pageOrder.empty()) {
209 return;
210 }
211 setCurrentPage(pageOrder.front());
212 }
213
setCurrentPage(int id)214 void Wizard::setCurrentPage(int id)
215 {
216 d->currentId = id;
217 if (id == InvalidPage) {
218 return;
219 }
220 d->updateHeader();
221 d->updateButtonStates();
222 }
223
setPageVisible(int id,bool visible)224 void Wizard::setPageVisible(int id, bool visible)
225 {
226 if (visible) {
227 d->hiddenPages.erase(id);
228 } else {
229 d->hiddenPages.insert(id);
230 }
231 if (currentPage() == id && !visible) {
232 next();
233 }
234 }
235
currentPage() const236 int Wizard::currentPage() const
237 {
238 return d->currentId;
239 }
240
canGoToNextPage() const241 bool Wizard::canGoToNextPage() const
242 {
243 const WizardPage *const current = currentPageWidget();
244 return current ? current->isComplete() : false;
245 }
246
canGoToPreviousPage() const247 bool Wizard::canGoToPreviousPage() const
248 {
249 const int prev = d->previousPage();
250 if (prev == InvalidPage) {
251 return false;
252 }
253 const WizardPage *const prevPage = page(prev);
254 Q_ASSERT(prevPage);
255 return !prevPage->isCommitPage();
256 }
257
next()258 void Wizard::next()
259 {
260 WizardPage *const current = currentPageWidget();
261 if (current) {
262 current->onNext();
263 }
264 onNext(d->currentId);
265 auto it = Kleo::binary_find(d->pageOrder.begin(), d->pageOrder.end(), d->currentId);
266 Q_ASSERT(it != d->pageOrder.end());
267
268 do {
269 ++it;
270 } while (d->hiddenPages.find(*it) != d->hiddenPages.end());
271
272 if (it == d->pageOrder.end()) { // "Finish"
273 d->currentId = InvalidPage;
274 close();
275 } else { // "next"
276 setCurrentPage(*it);
277 }
278 }
279
previousPage() const280 int Wizard::Private::previousPage() const
281 {
282 if (pageOrder.empty()) {
283 return InvalidPage;
284 }
285
286 auto it = Kleo::binary_find(pageOrder.begin(), pageOrder.end(), currentId);
287 if (it == pageOrder.begin() || it == pageOrder.end()) {
288 return InvalidPage;
289 }
290
291 do {
292 --it;
293 } while (it != pageOrder.begin() && hiddenPages.find(*it) != hiddenPages.end());
294 return *it;
295 }
296
back()297 void Wizard::back()
298 {
299 onBack(d->currentId);
300 const int prev = d->previousPage();
301 if (prev == InvalidPage) {
302 return;
303 }
304 setCurrentPage(prev);
305 }
306
page(int id) const307 const WizardPage *Wizard::page(int id) const
308 {
309 if (id == InvalidPage) {
310 return nullptr;
311 }
312
313 const auto it = d->idToPage.find(id);
314 kleo_assert(it != d->idToPage.end());
315 return (*it).second;
316 }
317
currentPageWidget() const318 const WizardPage *Wizard::currentPageWidget() const
319 {
320 return page(d->currentId);
321 }
322
currentPageWidget()323 WizardPage *Wizard::currentPageWidget()
324 {
325 return page(d->currentId);
326 }
327
onNext(int currentId)328 void Wizard::onNext(int currentId)
329 {
330 Q_UNUSED(currentId)
331 }
332
onBack(int currentId)333 void Wizard::onBack(int currentId)
334 {
335 Q_UNUSED(currentId)
336 }
337
page(int id)338 WizardPage *Wizard::page(int id)
339 {
340 if (id == InvalidPage) {
341 return nullptr;
342 }
343
344 const auto it = d->idToPage.find(id);
345 kleo_assert(it != d->idToPage.end());
346 return (*it).second;
347 }
348
349 #include "moc_wizard.cpp"
350