1/*
2 This file is part of the KDE libraries
3 SPDX-FileCopyrightText: 2000 David Faure <faure@kde.org>
4 SPDX-FileCopyrightText: 2007 Olivier Goffart <ogoffart at kde.org>
5 SPDX-FileCopyrightText: 2020 Harald Sitter <sitter@kde.org>
6
7 SPDX-License-Identifier: LGPL-2.0-only
8*/
9#include "kpassworddialog.h"
10
11#include <QCheckBox>
12#include <QComboBox>
13#include <QLabel>
14#include <QLayout>
15#include <QPushButton>
16#include <QScreen>
17#include <QStyleOption>
18#include <QTimer>
19
20#include <ktitlewidget.h>
21
22#include "ui_kpassworddialog.h"
23
24/** @internal */
25class KPasswordDialogPrivate
26{
27public:
28 KPasswordDialogPrivate(KPasswordDialog *qq)
29 : q(qq)
30 {
31 }
32
33 void actuallyAccept();
34 void activated(const QString &userName);
35
36 void updateFields();
37 void init();
38
39 KPasswordDialog *const q;
40 Ui_KPasswordDialog ui;
41 QMap<QString, QString> knownLogins;
42 QComboBox *userEditCombo = nullptr;
43 QIcon icon;
44 KPasswordDialog::KPasswordDialogFlags m_flags;
45 unsigned int commentRow = 0;
46};
47
48KPasswordDialog::KPasswordDialog(QWidget *parent, const KPasswordDialogFlags &flags)
49 : QDialog(parent)
50 , d(new KPasswordDialogPrivate(this))
51{
52 setWindowTitle(tr(s: "Password", c: "@title:window"));
53 setWindowIcon(QIcon::fromTheme(QStringLiteral("dialog-password"), fallback: windowIcon()));
54 d->m_flags = flags;
55 d->init();
56}
57
58KPasswordDialog::~KPasswordDialog() = default;
59
60void KPasswordDialogPrivate::updateFields()
61{
62 if (m_flags & KPasswordDialog::UsernameReadOnly) {
63 ui.userEdit->setReadOnly(true);
64 ui.credentialsGroup->setFocusProxy(ui.passEdit);
65 }
66 ui.domainEdit->setReadOnly((m_flags & KPasswordDialog::DomainReadOnly));
67 ui.credentialsGroup->setEnabled(!q->anonymousMode());
68}
69
70void KPasswordDialogPrivate::init()
71{
72 ui.setupUi(q);
73 ui.buttonBox->setStandardButtons(QDialogButtonBox::Ok | QDialogButtonBox::Cancel);
74 ui.errorMessage->setHidden(true);
75
76 ui.userEditContextHelpButton->hide();
77 ui.userEditContextHelpButton->setFlat(true);
78 ui.userEditContextHelpButton->setIcon(QIcon::fromTheme(QStringLiteral("help-contextual")));
79 ui.userEditContextHelpButton->setText(QString());
80 const QString description = QApplication::translate(context: "KPasswordDialog", key: "Show Contextual Help");
81 ui.userEditContextHelpButton->setAccessibleName(description);
82 ui.userEditContextHelpButton->setToolTip(description);
83 QObject::connect(sender: ui.userEditContextHelpButton, signal: &QPushButton::released, context: q, slot: [this] {
84 QEvent ev(QEvent::WhatsThis);
85 qApp->sendEvent(receiver: ui.userEdit, event: &ev);
86 });
87
88 // Row 4: Username field
89 if (m_flags & KPasswordDialog::ShowUsernameLine) {
90 ui.userEdit->setFocus();
91 ui.credentialsGroup->setFocusProxy(ui.userEdit);
92 QObject::connect(sender: ui.userEdit, signal: &QLineEdit::returnPressed, context: ui.passEdit, slot: qOverload<>(&QWidget::setFocus));
93 } else {
94 ui.userNameLabel->hide();
95 ui.userEdit->hide();
96 ui.domainLabel->hide();
97 ui.domainEdit->hide();
98 ui.passEdit->setFocus();
99 ui.credentialsGroup->setFocusProxy(ui.passEdit);
100 ui.prompt->setText(QApplication::translate(context: "KPasswordDialog", key: "Supply a password below."));
101 }
102
103 if (!(m_flags & KPasswordDialog::ShowAnonymousLoginCheckBox)) {
104 ui.anonymousRadioButton->hide();
105 ui.usePasswordButton->hide();
106 }
107
108 if (!(m_flags & KPasswordDialog::ShowDomainLine)) {
109 ui.domainLabel->hide();
110 ui.domainEdit->hide();
111 }
112
113 if (!(m_flags & KPasswordDialog::ShowKeepPassword)) {
114 ui.keepCheckBox->hide();
115 }
116
117 updateFields();
118
119 QRect desktop = q->topLevelWidget()->screen()->geometry();
120 q->setMinimumWidth(qMin(a: 1000, b: qMax(a: q->sizeHint().width(), b: desktop.width() / 4)));
121 q->setIcon(QIcon::fromTheme(QStringLiteral("dialog-password")));
122}
123
124void KPasswordDialog::setIcon(const QIcon &icon)
125{
126 d->icon = icon;
127
128 QStyleOption option;
129 option.initFrom(w: this);
130 const int iconSize = style()->pixelMetric(metric: QStyle::PM_MessageBoxIconSize, option: &option, widget: this);
131 d->ui.pixmapLabel->setPixmap(icon.pixmap(extent: iconSize));
132}
133
134QIcon KPasswordDialog::icon() const
135{
136 return d->icon;
137}
138
139void KPasswordDialog::setUsername(const QString &user)
140{
141 d->ui.userEdit->setText(user);
142 if (user.isEmpty()) {
143 return;
144 }
145
146 d->activated(userName: user);
147 if (d->ui.userEdit->isVisibleTo(this)) {
148 d->ui.passEdit->setFocus();
149 }
150}
151
152QString KPasswordDialog::username() const
153{
154 return d->ui.userEdit->text();
155}
156
157QString KPasswordDialog::password() const
158{
159 return d->ui.passEdit->password();
160}
161
162void KPasswordDialog::setDomain(const QString &domain)
163{
164 d->ui.domainEdit->setText(domain);
165}
166
167QString KPasswordDialog::domain() const
168{
169 return d->ui.domainEdit->text();
170}
171
172void KPasswordDialog::setAnonymousMode(bool anonymous)
173{
174 if (anonymous && !(d->m_flags & KPasswordDialog::ShowAnonymousLoginCheckBox)) {
175 // This is an error case, but we can at least let user see what's about
176 // to happen if they proceed.
177 d->ui.anonymousRadioButton->setVisible(true);
178
179 d->ui.usePasswordButton->setVisible(true);
180 d->ui.usePasswordButton->setEnabled(false);
181 }
182
183 d->ui.anonymousRadioButton->setChecked(anonymous);
184}
185
186bool KPasswordDialog::anonymousMode() const
187{
188 return d->ui.anonymousRadioButton->isChecked();
189}
190
191void KPasswordDialog::setKeepPassword(bool b)
192{
193 d->ui.keepCheckBox->setChecked(b);
194}
195
196bool KPasswordDialog::keepPassword() const
197{
198 return d->ui.keepCheckBox->isChecked();
199}
200
201void KPasswordDialog::addCommentLine(const QString &label, const QString &comment)
202{
203 int gridMarginLeft;
204 int gridMarginTop;
205 int gridMarginRight;
206 int gridMarginBottom;
207 d->ui.formLayout->getContentsMargins(left: &gridMarginLeft, top: &gridMarginTop, right: &gridMarginRight, bottom: &gridMarginBottom);
208
209 int spacing = d->ui.formLayout->horizontalSpacing();
210 if (spacing < 0) {
211 // same inter-column spacing for all rows, see comment in qformlayout.cpp
212 spacing = style()->combinedLayoutSpacing(controls1: QSizePolicy::Label, controls2: QSizePolicy::LineEdit, orientation: Qt::Horizontal, option: nullptr, widget: this);
213 }
214
215 QLabel *c = new QLabel(comment, this);
216 c->setWordWrap(true);
217 c->setTextInteractionFlags(Qt::TextBrowserInteraction);
218
219 d->ui.formLayout->insertRow(row: d->commentRow, labelText: label, field: c);
220 ++d->commentRow;
221
222 // cycle through column 0 widgets and see the max width so we can set the minimum height of
223 // column 2 wordwrapable labels
224 int firstColumnWidth = 0;
225 for (int i = 0; i < d->ui.formLayout->rowCount(); ++i) {
226 QLayoutItem *li = d->ui.formLayout->itemAt(row: i, role: QFormLayout::LabelRole);
227 if (li) {
228 QWidget *w = li->widget();
229 if (w && !w->isHidden()) {
230 firstColumnWidth = qMax(a: firstColumnWidth, b: w->sizeHint().width());
231 }
232 }
233 }
234 for (int i = 0; i < d->ui.formLayout->rowCount(); ++i) {
235 QLayoutItem *li = d->ui.formLayout->itemAt(row: i, role: QFormLayout::FieldRole);
236 if (li) {
237 QLabel *l = qobject_cast<QLabel *>(object: li->widget());
238 if (l && l->wordWrap()) {
239 auto *style = this->style();
240 const int leftMargin = style->pixelMetric(metric: QStyle::PM_LayoutLeftMargin);
241 const int rightMargin = style->pixelMetric(metric: QStyle::PM_LayoutRightMargin);
242 int w = sizeHint().width() - firstColumnWidth - leftMargin - rightMargin - gridMarginLeft - gridMarginRight - spacing;
243 l->setMinimumSize(minw: w, minh: l->heightForWidth(w));
244 }
245 }
246 }
247}
248
249void KPasswordDialog::showErrorMessage(const QString &message, const ErrorType type)
250{
251 d->ui.errorMessage->setText(text: message, type: KTitleWidget::ErrorMessage);
252
253 QFont bold = font();
254 bold.setBold(true);
255 switch (type) {
256 case PasswordError:
257 d->ui.passwordLabel->setFont(bold);
258 d->ui.passEdit->clear();
259 d->ui.passEdit->setFocus();
260 break;
261 case UsernameError:
262 if (d->ui.userEdit->isVisibleTo(this)) {
263 d->ui.userNameLabel->setFont(bold);
264 d->ui.userEdit->setFocus();
265 }
266 break;
267 case DomainError:
268 if (d->ui.domainEdit->isVisibleTo(this)) {
269 d->ui.domainLabel->setFont(bold);
270 d->ui.domainEdit->setFocus();
271 }
272 break;
273 case FatalError:
274 d->ui.userNameLabel->setEnabled(false);
275 d->ui.userEdit->setEnabled(false);
276 d->ui.passwordLabel->setEnabled(false);
277 d->ui.passEdit->setEnabled(false);
278 d->ui.keepCheckBox->setEnabled(false);
279 d->ui.buttonBox->button(which: QDialogButtonBox::Ok)->setEnabled(false);
280 break;
281 default:
282 break;
283 }
284 adjustSize();
285}
286
287void KPasswordDialog::setPrompt(const QString &prompt)
288{
289 d->ui.prompt->setText(prompt);
290 d->ui.prompt->setWordWrap(true);
291 auto *style = this->style();
292 const int leftMarginHint = style->pixelMetric(metric: QStyle::PM_LayoutLeftMargin);
293 const int rightMarginHint = style->pixelMetric(metric: QStyle::PM_LayoutRightMargin);
294 d->ui.prompt->setMinimumHeight(d->ui.prompt->heightForWidth(width() - leftMarginHint - rightMarginHint));
295}
296
297QString KPasswordDialog::prompt() const
298{
299 return d->ui.prompt->text();
300}
301
302void KPasswordDialog::setPassword(const QString &p)
303{
304 d->ui.passEdit->setPassword(p);
305}
306
307void KPasswordDialog::setUsernameReadOnly(bool readOnly)
308{
309 d->ui.userEdit->setReadOnly(readOnly);
310
311 if (readOnly && d->ui.userEdit->hasFocus()) {
312 d->ui.passEdit->setFocus();
313 }
314}
315
316void KPasswordDialog::setKnownLogins(const QMap<QString, QString> &knownLogins)
317{
318 const int nr = knownLogins.count();
319 if (nr == 0) {
320 return;
321 }
322
323 if (nr == 1) {
324 d->ui.userEdit->setText(knownLogins.begin().key());
325 setPassword(knownLogins.begin().value());
326 return;
327 }
328
329 Q_ASSERT(!d->ui.userEdit->isReadOnly());
330 if (!d->userEditCombo) {
331 int row = -1;
332 QFormLayout::ItemRole userEditRole = QFormLayout::FieldRole;
333
334 d->ui.formLayout->getWidgetPosition(widget: d->ui.userEdit, rowPtr: &row, rolePtr: &userEditRole);
335 d->ui.formLayout->removeWidget(w: d->ui.userEdit);
336 delete d->ui.userEdit;
337 d->userEditCombo = new QComboBox(d->ui.credentialsGroup);
338 d->userEditCombo->setEditable(true);
339 d->ui.userEdit = d->userEditCombo->lineEdit();
340 d->ui.userNameLabel->setBuddy(d->userEditCombo);
341 d->ui.formLayout->setWidget(row: row > -1 ? row : 0, role: userEditRole, widget: d->userEditCombo);
342
343 setTabOrder(d->ui.userEdit, d->ui.anonymousRadioButton);
344 setTabOrder(d->ui.anonymousRadioButton, d->ui.domainEdit);
345 setTabOrder(d->ui.domainEdit, d->ui.passEdit);
346 setTabOrder(d->ui.passEdit, d->ui.keepCheckBox);
347 connect(sender: d->ui.userEdit, signal: &QLineEdit::returnPressed, context: d->ui.passEdit, slot: qOverload<>(&QWidget::setFocus));
348 }
349
350 d->knownLogins = knownLogins;
351 d->userEditCombo->addItems(texts: knownLogins.keys());
352 d->userEditCombo->setFocus();
353
354 connect(sender: d->userEditCombo, signal: &QComboBox::textActivated, context: this, slot: [this](const QString &text) {
355 d->activated(userName: text);
356 });
357}
358
359#if KWIDGETSADDONS_ENABLE_DEPRECATED_SINCE(6, 0)
360#pragma GCC diagnostic push
361#pragma GCC diagnostic ignored "-Wdeprecated-declarations"
362void KPasswordDialog::setRevealPasswordAvailable(bool reveal)
363{
364 d->ui.passEdit->setRevealPasswordAvailable(reveal);
365}
366
367bool KPasswordDialog::isRevealPasswordAvailable() const
368{
369 return d->ui.passEdit->isRevealPasswordAvailable();
370}
371#pragma GCC diagnostic pop
372#endif
373
374KPassword::RevealMode KPasswordDialog::revealPasswordMode() const
375{
376 return d->ui.passEdit->revealPasswordMode();
377}
378
379void KPasswordDialog::setRevealPasswordMode(KPassword::RevealMode revealPasswordMode)
380{
381 d->ui.passEdit->setRevealPasswordMode(revealPasswordMode);
382}
383
384void KPasswordDialogPrivate::activated(const QString &userName)
385{
386 QMap<QString, QString>::ConstIterator it = knownLogins.constFind(key: userName);
387 if (it != knownLogins.constEnd()) {
388 q->setPassword(it.value());
389 }
390}
391
392void KPasswordDialog::accept()
393{
394 if (!d->ui.errorMessage->isHidden()) {
395 d->ui.errorMessage->setText(text: QString());
396 }
397
398 // reset the font in case we had an error previously
399 if (!d->ui.passwordLabel->isHidden()) {
400 d->ui.passwordLabel->setFont(font());
401 d->ui.userNameLabel->setFont(font());
402 }
403
404 // we do this to allow the error message, if any, to go away
405 // checkPassword() may block for a period of time
406 QTimer::singleShot(interval: 0, receiver: this, slot: [this] {
407 d->actuallyAccept();
408 });
409}
410
411void KPasswordDialogPrivate::actuallyAccept()
412{
413 if (!q->checkPassword()) {
414 return;
415 }
416
417 bool keep = ui.keepCheckBox->isVisibleTo(q) && ui.keepCheckBox->isChecked();
418 Q_EMIT q->gotPassword(password: q->password(), keep);
419
420 if (ui.userEdit->isVisibleTo(q)) {
421 Q_EMIT q->gotUsernameAndPassword(username: q->username(), password: q->password(), keep);
422 }
423
424 q->QDialog::accept();
425}
426
427bool KPasswordDialog::checkPassword()
428{
429 return true;
430}
431
432QDialogButtonBox *KPasswordDialog::buttonBox() const
433{
434 return d->ui.buttonBox;
435}
436
437void KPasswordDialog::setUsernameContextHelp(const QString &help)
438{
439 d->ui.userEditContextHelpButton->setVisible(true);
440 d->ui.userEdit->setWhatsThis(help);
441}
442
443#include "moc_kpassworddialog.cpp"
444

source code of kwidgetsaddons/src/kpassworddialog.cpp