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 */
25 class KPasswordDialogPrivate
26 {
27 public:
KPasswordDialogPrivate(KPasswordDialog * qq)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
KPasswordDialog(QWidget * parent,const KPasswordDialogFlags & flags)48 KPasswordDialog::KPasswordDialog(QWidget *parent, const KPasswordDialogFlags &flags)
49 : QDialog(parent)
50 , d(new KPasswordDialogPrivate(this))
51 {
52 setWindowTitle(tr("Password", "@title:window"));
53 setWindowIcon(QIcon::fromTheme(QStringLiteral("dialog-password"), windowIcon()));
54 d->m_flags = flags;
55 d->init();
56 }
57
58 KPasswordDialog::~KPasswordDialog() = default;
59
updateFields()60 void 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
init()70 void 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("KPasswordDialog", "Show Contextual Help");
81 ui.userEditContextHelpButton->setAccessibleName(description);
82 ui.userEditContextHelpButton->setToolTip(description);
83 QObject::connect(ui.userEditContextHelpButton, &QPushButton::released, q, [this] {
84 QEvent ev(QEvent::WhatsThis);
85 qApp->sendEvent(ui.userEdit, &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(ui.userEdit, &QLineEdit::returnPressed, ui.passEdit, 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("KPasswordDialog", "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(1000, qMax(q->sizeHint().width(), desktop.width() / 4)));
121 q->setIcon(QIcon::fromTheme(QStringLiteral("dialog-password")));
122 }
123
setIcon(const QIcon & icon)124 void KPasswordDialog::setIcon(const QIcon &icon)
125 {
126 d->icon = icon;
127
128 QStyleOption option;
129 option.initFrom(this);
130 const int iconSize = style()->pixelMetric(QStyle::PM_MessageBoxIconSize, &option, this);
131 d->ui.pixmapLabel->setPixmap(icon.pixmap(iconSize));
132 }
133
icon() const134 QIcon KPasswordDialog::icon() const
135 {
136 return d->icon;
137 }
138
139 #if KWIDGETSADDONS_BUILD_DEPRECATED_SINCE(5, 63)
setPixmap(const QPixmap & pixmap)140 void KPasswordDialog::setPixmap(const QPixmap &pixmap)
141 {
142 d->ui.pixmapLabel->setPixmap(pixmap);
143 }
144
pixmap() const145 QPixmap KPasswordDialog::pixmap() const
146 {
147 return d->ui.pixmapLabel->pixmap(Qt::ReturnByValue);
148 }
149 #endif
150
setUsername(const QString & user)151 void KPasswordDialog::setUsername(const QString &user)
152 {
153 d->ui.userEdit->setText(user);
154 if (user.isEmpty()) {
155 return;
156 }
157
158 d->activated(user);
159 if (d->ui.userEdit->isVisibleTo(this)) {
160 d->ui.passEdit->setFocus();
161 }
162 }
163
username() const164 QString KPasswordDialog::username() const
165 {
166 return d->ui.userEdit->text();
167 }
168
password() const169 QString KPasswordDialog::password() const
170 {
171 return d->ui.passEdit->password();
172 }
173
setDomain(const QString & domain)174 void KPasswordDialog::setDomain(const QString &domain)
175 {
176 d->ui.domainEdit->setText(domain);
177 }
178
domain() const179 QString KPasswordDialog::domain() const
180 {
181 return d->ui.domainEdit->text();
182 }
183
setAnonymousMode(bool anonymous)184 void KPasswordDialog::setAnonymousMode(bool anonymous)
185 {
186 if (anonymous && !(d->m_flags & KPasswordDialog::ShowAnonymousLoginCheckBox)) {
187 // This is an error case, but we can at least let user see what's about
188 // to happen if they proceed.
189 d->ui.anonymousRadioButton->setVisible(true);
190
191 d->ui.usePasswordButton->setVisible(true);
192 d->ui.usePasswordButton->setEnabled(false);
193 }
194
195 d->ui.anonymousRadioButton->setChecked(anonymous);
196 }
197
anonymousMode() const198 bool KPasswordDialog::anonymousMode() const
199 {
200 return d->ui.anonymousRadioButton->isChecked();
201 }
202
setKeepPassword(bool b)203 void KPasswordDialog::setKeepPassword(bool b)
204 {
205 d->ui.keepCheckBox->setChecked(b);
206 }
207
keepPassword() const208 bool KPasswordDialog::keepPassword() const
209 {
210 return d->ui.keepCheckBox->isChecked();
211 }
212
addCommentLine(const QString & label,const QString & comment)213 void KPasswordDialog::addCommentLine(const QString &label, const QString &comment)
214 {
215 int gridMarginLeft;
216 int gridMarginTop;
217 int gridMarginRight;
218 int gridMarginBottom;
219 d->ui.formLayout->getContentsMargins(&gridMarginLeft, &gridMarginTop, &gridMarginRight, &gridMarginBottom);
220
221 int spacing = d->ui.formLayout->horizontalSpacing();
222 if (spacing < 0) {
223 // same inter-column spacing for all rows, see comment in qformlayout.cpp
224 spacing = style()->combinedLayoutSpacing(QSizePolicy::Label, QSizePolicy::LineEdit, Qt::Horizontal, nullptr, this);
225 }
226
227 QLabel *c = new QLabel(comment, this);
228 c->setWordWrap(true);
229
230 d->ui.formLayout->insertRow(d->commentRow, label, c);
231 ++d->commentRow;
232
233 // cycle through column 0 widgets and see the max width so we can set the minimum height of
234 // column 2 wordwrapable labels
235 int firstColumnWidth = 0;
236 for (int i = 0; i < d->ui.formLayout->rowCount(); ++i) {
237 QLayoutItem *li = d->ui.formLayout->itemAt(i, QFormLayout::LabelRole);
238 if (li) {
239 QWidget *w = li->widget();
240 if (w && !w->isHidden()) {
241 firstColumnWidth = qMax(firstColumnWidth, w->sizeHint().width());
242 }
243 }
244 }
245 for (int i = 0; i < d->ui.formLayout->rowCount(); ++i) {
246 QLayoutItem *li = d->ui.formLayout->itemAt(i, QFormLayout::FieldRole);
247 if (li) {
248 QLabel *l = qobject_cast<QLabel *>(li->widget());
249 if (l && l->wordWrap()) {
250 auto *style = this->style();
251 const int leftMargin = style->pixelMetric(QStyle::PM_LayoutLeftMargin);
252 const int rightMargin = style->pixelMetric(QStyle::PM_LayoutRightMargin);
253 int w = sizeHint().width() - firstColumnWidth - leftMargin - rightMargin - gridMarginLeft - gridMarginRight - spacing;
254 l->setMinimumSize(w, l->heightForWidth(w));
255 }
256 }
257 }
258 }
259
showErrorMessage(const QString & message,const ErrorType type)260 void KPasswordDialog::showErrorMessage(const QString &message, const ErrorType type)
261 {
262 d->ui.errorMessage->setText(message, KTitleWidget::ErrorMessage);
263
264 QFont bold = font();
265 bold.setBold(true);
266 switch (type) {
267 case PasswordError:
268 d->ui.passwordLabel->setFont(bold);
269 d->ui.passEdit->clear();
270 d->ui.passEdit->setFocus();
271 break;
272 case UsernameError:
273 if (d->ui.userEdit->isVisibleTo(this)) {
274 d->ui.userNameLabel->setFont(bold);
275 d->ui.userEdit->setFocus();
276 }
277 break;
278 case DomainError:
279 if (d->ui.domainEdit->isVisibleTo(this)) {
280 d->ui.domainLabel->setFont(bold);
281 d->ui.domainEdit->setFocus();
282 }
283 break;
284 case FatalError:
285 d->ui.userNameLabel->setEnabled(false);
286 d->ui.userEdit->setEnabled(false);
287 d->ui.passwordLabel->setEnabled(false);
288 d->ui.passEdit->setEnabled(false);
289 d->ui.keepCheckBox->setEnabled(false);
290 d->ui.buttonBox->button(QDialogButtonBox::Ok)->setEnabled(false);
291 break;
292 default:
293 break;
294 }
295 adjustSize();
296 }
297
setPrompt(const QString & prompt)298 void KPasswordDialog::setPrompt(const QString &prompt)
299 {
300 d->ui.prompt->setText(prompt);
301 d->ui.prompt->setWordWrap(true);
302 auto *style = this->style();
303 const int leftMarginHint = style->pixelMetric(QStyle::PM_LayoutLeftMargin);
304 const int rightMarginHint = style->pixelMetric(QStyle::PM_LayoutRightMargin);
305 d->ui.prompt->setMinimumHeight(d->ui.prompt->heightForWidth(width() - leftMarginHint - rightMarginHint));
306 }
307
prompt() const308 QString KPasswordDialog::prompt() const
309 {
310 return d->ui.prompt->text();
311 }
312
setPassword(const QString & p)313 void KPasswordDialog::setPassword(const QString &p)
314 {
315 d->ui.passEdit->setPassword(p);
316 }
317
setUsernameReadOnly(bool readOnly)318 void KPasswordDialog::setUsernameReadOnly(bool readOnly)
319 {
320 d->ui.userEdit->setReadOnly(readOnly);
321
322 if (readOnly && d->ui.userEdit->hasFocus()) {
323 d->ui.passEdit->setFocus();
324 }
325 }
326
setKnownLogins(const QMap<QString,QString> & knownLogins)327 void KPasswordDialog::setKnownLogins(const QMap<QString, QString> &knownLogins)
328 {
329 const int nr = knownLogins.count();
330 if (nr == 0) {
331 return;
332 }
333
334 if (nr == 1) {
335 d->ui.userEdit->setText(knownLogins.begin().key());
336 setPassword(knownLogins.begin().value());
337 return;
338 }
339
340 Q_ASSERT(!d->ui.userEdit->isReadOnly());
341 if (!d->userEditCombo) {
342 int row = -1;
343 QFormLayout::ItemRole userEditRole = QFormLayout::FieldRole;
344
345 d->ui.formLayout->getWidgetPosition(d->ui.userEdit, &row, &userEditRole);
346 d->ui.formLayout->removeWidget(d->ui.userEdit);
347 delete d->ui.userEdit;
348 d->userEditCombo = new QComboBox(d->ui.credentialsGroup);
349 d->userEditCombo->setEditable(true);
350 d->ui.userEdit = d->userEditCombo->lineEdit();
351 d->ui.userNameLabel->setBuddy(d->userEditCombo);
352 d->ui.formLayout->setWidget(row > -1 ? row : 0, userEditRole, d->userEditCombo);
353
354 setTabOrder(d->ui.userEdit, d->ui.anonymousRadioButton);
355 setTabOrder(d->ui.anonymousRadioButton, d->ui.domainEdit);
356 setTabOrder(d->ui.domainEdit, d->ui.passEdit);
357 setTabOrder(d->ui.passEdit, d->ui.keepCheckBox);
358 connect(d->ui.userEdit, &QLineEdit::returnPressed, d->ui.passEdit, qOverload<>(&QWidget::setFocus));
359 }
360
361 d->knownLogins = knownLogins;
362 d->userEditCombo->addItems(knownLogins.keys());
363 d->userEditCombo->setFocus();
364
365 connect(d->userEditCombo, &QComboBox::textActivated, this, [this](const QString &text) {
366 d->activated(text);
367 });
368 }
369
setRevealPasswordAvailable(bool reveal)370 void KPasswordDialog::setRevealPasswordAvailable(bool reveal)
371 {
372 d->ui.passEdit->setRevealPasswordAvailable(reveal);
373 }
374
isRevealPasswordAvailable() const375 bool KPasswordDialog::isRevealPasswordAvailable() const
376 {
377 return d->ui.passEdit->isRevealPasswordAvailable();
378 }
379
activated(const QString & userName)380 void KPasswordDialogPrivate::activated(const QString &userName)
381 {
382 QMap<QString, QString>::ConstIterator it = knownLogins.constFind(userName);
383 if (it != knownLogins.constEnd()) {
384 q->setPassword(it.value());
385 }
386 }
387
accept()388 void KPasswordDialog::accept()
389 {
390 if (!d->ui.errorMessage->isHidden()) {
391 d->ui.errorMessage->setText(QString());
392 }
393
394 // reset the font in case we had an error previously
395 if (!d->ui.passwordLabel->isHidden()) {
396 d->ui.passwordLabel->setFont(font());
397 d->ui.userNameLabel->setFont(font());
398 }
399
400 // we do this to allow the error message, if any, to go away
401 // checkPassword() may block for a period of time
402 QTimer::singleShot(0, this, [this] {
403 d->actuallyAccept();
404 });
405 }
406
actuallyAccept()407 void KPasswordDialogPrivate::actuallyAccept()
408 {
409 if (!q->checkPassword()) {
410 return;
411 }
412
413 bool keep = ui.keepCheckBox->isVisibleTo(q) && ui.keepCheckBox->isChecked();
414 Q_EMIT q->gotPassword(q->password(), keep);
415
416 if (ui.userEdit->isVisibleTo(q)) {
417 Q_EMIT q->gotUsernameAndPassword(q->username(), q->password(), keep);
418 }
419
420 q->QDialog::accept();
421 }
422
checkPassword()423 bool KPasswordDialog::checkPassword()
424 {
425 return true;
426 }
427
buttonBox() const428 QDialogButtonBox *KPasswordDialog::buttonBox() const
429 {
430 return d->ui.buttonBox;
431 }
432
setUsernameContextHelp(const QString & help)433 void KPasswordDialog::setUsernameContextHelp(const QString &help)
434 {
435 d->ui.userEditContextHelpButton->setVisible(true);
436 d->ui.userEdit->setWhatsThis(help);
437 }
438
439 #include "moc_kpassworddialog.cpp"
440