File indexing completed on 2024-04-28 03:59:11

0001 /*
0002     This file is part of the KDE libraries
0003     SPDX-FileCopyrightText: 2000 David Faure <faure@kde.org>
0004     SPDX-FileCopyrightText: 2007 Olivier Goffart <ogoffart at kde.org>
0005     SPDX-FileCopyrightText: 2020 Harald Sitter <sitter@kde.org>
0006 
0007     SPDX-License-Identifier: LGPL-2.0-only
0008 */
0009 #include "kpassworddialog.h"
0010 
0011 #include <QCheckBox>
0012 #include <QComboBox>
0013 #include <QLabel>
0014 #include <QLayout>
0015 #include <QPushButton>
0016 #include <QScreen>
0017 #include <QStyleOption>
0018 #include <QTimer>
0019 
0020 #include <ktitlewidget.h>
0021 
0022 #include "ui_kpassworddialog.h"
0023 
0024 /** @internal */
0025 class KPasswordDialogPrivate
0026 {
0027 public:
0028     KPasswordDialogPrivate(KPasswordDialog *qq)
0029         : q(qq)
0030     {
0031     }
0032 
0033     void actuallyAccept();
0034     void activated(const QString &userName);
0035 
0036     void updateFields();
0037     void init();
0038 
0039     KPasswordDialog *const q;
0040     Ui_KPasswordDialog ui;
0041     QMap<QString, QString> knownLogins;
0042     QComboBox *userEditCombo = nullptr;
0043     QIcon icon;
0044     KPasswordDialog::KPasswordDialogFlags m_flags;
0045     unsigned int commentRow = 0;
0046 };
0047 
0048 KPasswordDialog::KPasswordDialog(QWidget *parent, const KPasswordDialogFlags &flags)
0049     : QDialog(parent)
0050     , d(new KPasswordDialogPrivate(this))
0051 {
0052     setWindowTitle(tr("Password", "@title:window"));
0053     setWindowIcon(QIcon::fromTheme(QStringLiteral("dialog-password"), windowIcon()));
0054     d->m_flags = flags;
0055     d->init();
0056 }
0057 
0058 KPasswordDialog::~KPasswordDialog() = default;
0059 
0060 void KPasswordDialogPrivate::updateFields()
0061 {
0062     if (m_flags & KPasswordDialog::UsernameReadOnly) {
0063         ui.userEdit->setReadOnly(true);
0064         ui.credentialsGroup->setFocusProxy(ui.passEdit);
0065     }
0066     ui.domainEdit->setReadOnly((m_flags & KPasswordDialog::DomainReadOnly));
0067     ui.credentialsGroup->setEnabled(!q->anonymousMode());
0068 }
0069 
0070 void KPasswordDialogPrivate::init()
0071 {
0072     ui.setupUi(q);
0073     ui.buttonBox->setStandardButtons(QDialogButtonBox::Ok | QDialogButtonBox::Cancel);
0074     ui.errorMessage->setHidden(true);
0075 
0076     ui.userEditContextHelpButton->hide();
0077     ui.userEditContextHelpButton->setFlat(true);
0078     ui.userEditContextHelpButton->setIcon(QIcon::fromTheme(QStringLiteral("help-contextual")));
0079     ui.userEditContextHelpButton->setText(QString());
0080     const QString description = QApplication::translate("KPasswordDialog", "Show Contextual Help");
0081     ui.userEditContextHelpButton->setAccessibleName(description);
0082     ui.userEditContextHelpButton->setToolTip(description);
0083     QObject::connect(ui.userEditContextHelpButton, &QPushButton::released, q, [this] {
0084         QEvent ev(QEvent::WhatsThis);
0085         qApp->sendEvent(ui.userEdit, &ev);
0086     });
0087 
0088     // Row 4: Username field
0089     if (m_flags & KPasswordDialog::ShowUsernameLine) {
0090         ui.userEdit->setFocus();
0091         ui.credentialsGroup->setFocusProxy(ui.userEdit);
0092         QObject::connect(ui.userEdit, &QLineEdit::returnPressed, ui.passEdit, qOverload<>(&QWidget::setFocus));
0093     } else {
0094         ui.userNameLabel->hide();
0095         ui.userEdit->hide();
0096         ui.domainLabel->hide();
0097         ui.domainEdit->hide();
0098         ui.passEdit->setFocus();
0099         ui.credentialsGroup->setFocusProxy(ui.passEdit);
0100         ui.prompt->setText(QApplication::translate("KPasswordDialog", "Supply a password below."));
0101     }
0102 
0103     if (!(m_flags & KPasswordDialog::ShowAnonymousLoginCheckBox)) {
0104         ui.anonymousRadioButton->hide();
0105         ui.usePasswordButton->hide();
0106     }
0107 
0108     if (!(m_flags & KPasswordDialog::ShowDomainLine)) {
0109         ui.domainLabel->hide();
0110         ui.domainEdit->hide();
0111     }
0112 
0113     if (!(m_flags & KPasswordDialog::ShowKeepPassword)) {
0114         ui.keepCheckBox->hide();
0115     }
0116 
0117     updateFields();
0118 
0119     QRect desktop = q->topLevelWidget()->screen()->geometry();
0120     q->setMinimumWidth(qMin(1000, qMax(q->sizeHint().width(), desktop.width() / 4)));
0121     q->setIcon(QIcon::fromTheme(QStringLiteral("dialog-password")));
0122 }
0123 
0124 void KPasswordDialog::setIcon(const QIcon &icon)
0125 {
0126     d->icon = icon;
0127 
0128     QStyleOption option;
0129     option.initFrom(this);
0130     const int iconSize = style()->pixelMetric(QStyle::PM_MessageBoxIconSize, &option, this);
0131     d->ui.pixmapLabel->setPixmap(icon.pixmap(iconSize));
0132 }
0133 
0134 QIcon KPasswordDialog::icon() const
0135 {
0136     return d->icon;
0137 }
0138 
0139 void KPasswordDialog::setUsername(const QString &user)
0140 {
0141     d->ui.userEdit->setText(user);
0142     if (user.isEmpty()) {
0143         return;
0144     }
0145 
0146     d->activated(user);
0147     if (d->ui.userEdit->isVisibleTo(this)) {
0148         d->ui.passEdit->setFocus();
0149     }
0150 }
0151 
0152 QString KPasswordDialog::username() const
0153 {
0154     return d->ui.userEdit->text();
0155 }
0156 
0157 QString KPasswordDialog::password() const
0158 {
0159     return d->ui.passEdit->password();
0160 }
0161 
0162 void KPasswordDialog::setDomain(const QString &domain)
0163 {
0164     d->ui.domainEdit->setText(domain);
0165 }
0166 
0167 QString KPasswordDialog::domain() const
0168 {
0169     return d->ui.domainEdit->text();
0170 }
0171 
0172 void KPasswordDialog::setAnonymousMode(bool anonymous)
0173 {
0174     if (anonymous && !(d->m_flags & KPasswordDialog::ShowAnonymousLoginCheckBox)) {
0175         // This is an error case, but we can at least let user see what's about
0176         // to happen if they proceed.
0177         d->ui.anonymousRadioButton->setVisible(true);
0178 
0179         d->ui.usePasswordButton->setVisible(true);
0180         d->ui.usePasswordButton->setEnabled(false);
0181     }
0182 
0183     d->ui.anonymousRadioButton->setChecked(anonymous);
0184 }
0185 
0186 bool KPasswordDialog::anonymousMode() const
0187 {
0188     return d->ui.anonymousRadioButton->isChecked();
0189 }
0190 
0191 void KPasswordDialog::setKeepPassword(bool b)
0192 {
0193     d->ui.keepCheckBox->setChecked(b);
0194 }
0195 
0196 bool KPasswordDialog::keepPassword() const
0197 {
0198     return d->ui.keepCheckBox->isChecked();
0199 }
0200 
0201 void KPasswordDialog::addCommentLine(const QString &label, const QString &comment)
0202 {
0203     int gridMarginLeft;
0204     int gridMarginTop;
0205     int gridMarginRight;
0206     int gridMarginBottom;
0207     d->ui.formLayout->getContentsMargins(&gridMarginLeft, &gridMarginTop, &gridMarginRight, &gridMarginBottom);
0208 
0209     int spacing = d->ui.formLayout->horizontalSpacing();
0210     if (spacing < 0) {
0211         // same inter-column spacing for all rows, see comment in qformlayout.cpp
0212         spacing = style()->combinedLayoutSpacing(QSizePolicy::Label, QSizePolicy::LineEdit, Qt::Horizontal, nullptr, this);
0213     }
0214 
0215     QLabel *c = new QLabel(comment, this);
0216     c->setWordWrap(true);
0217     c->setTextInteractionFlags(Qt::TextBrowserInteraction);
0218 
0219     d->ui.formLayout->insertRow(d->commentRow, label, c);
0220     ++d->commentRow;
0221 
0222     // cycle through column 0 widgets and see the max width so we can set the minimum height of
0223     // column 2 wordwrapable labels
0224     int firstColumnWidth = 0;
0225     for (int i = 0; i < d->ui.formLayout->rowCount(); ++i) {
0226         QLayoutItem *li = d->ui.formLayout->itemAt(i, QFormLayout::LabelRole);
0227         if (li) {
0228             QWidget *w = li->widget();
0229             if (w && !w->isHidden()) {
0230                 firstColumnWidth = qMax(firstColumnWidth, w->sizeHint().width());
0231             }
0232         }
0233     }
0234     for (int i = 0; i < d->ui.formLayout->rowCount(); ++i) {
0235         QLayoutItem *li = d->ui.formLayout->itemAt(i, QFormLayout::FieldRole);
0236         if (li) {
0237             QLabel *l = qobject_cast<QLabel *>(li->widget());
0238             if (l && l->wordWrap()) {
0239                 auto *style = this->style();
0240                 const int leftMargin = style->pixelMetric(QStyle::PM_LayoutLeftMargin);
0241                 const int rightMargin = style->pixelMetric(QStyle::PM_LayoutRightMargin);
0242                 int w = sizeHint().width() - firstColumnWidth - leftMargin - rightMargin - gridMarginLeft - gridMarginRight - spacing;
0243                 l->setMinimumSize(w, l->heightForWidth(w));
0244             }
0245         }
0246     }
0247 }
0248 
0249 void KPasswordDialog::showErrorMessage(const QString &message, const ErrorType type)
0250 {
0251     d->ui.errorMessage->setText(message, KTitleWidget::ErrorMessage);
0252 
0253     QFont bold = font();
0254     bold.setBold(true);
0255     switch (type) {
0256     case PasswordError:
0257         d->ui.passwordLabel->setFont(bold);
0258         d->ui.passEdit->clear();
0259         d->ui.passEdit->setFocus();
0260         break;
0261     case UsernameError:
0262         if (d->ui.userEdit->isVisibleTo(this)) {
0263             d->ui.userNameLabel->setFont(bold);
0264             d->ui.userEdit->setFocus();
0265         }
0266         break;
0267     case DomainError:
0268         if (d->ui.domainEdit->isVisibleTo(this)) {
0269             d->ui.domainLabel->setFont(bold);
0270             d->ui.domainEdit->setFocus();
0271         }
0272         break;
0273     case FatalError:
0274         d->ui.userNameLabel->setEnabled(false);
0275         d->ui.userEdit->setEnabled(false);
0276         d->ui.passwordLabel->setEnabled(false);
0277         d->ui.passEdit->setEnabled(false);
0278         d->ui.keepCheckBox->setEnabled(false);
0279         d->ui.buttonBox->button(QDialogButtonBox::Ok)->setEnabled(false);
0280         break;
0281     default:
0282         break;
0283     }
0284     adjustSize();
0285 }
0286 
0287 void KPasswordDialog::setPrompt(const QString &prompt)
0288 {
0289     d->ui.prompt->setText(prompt);
0290     d->ui.prompt->setWordWrap(true);
0291     auto *style = this->style();
0292     const int leftMarginHint = style->pixelMetric(QStyle::PM_LayoutLeftMargin);
0293     const int rightMarginHint = style->pixelMetric(QStyle::PM_LayoutRightMargin);
0294     d->ui.prompt->setMinimumHeight(d->ui.prompt->heightForWidth(width() - leftMarginHint - rightMarginHint));
0295 }
0296 
0297 QString KPasswordDialog::prompt() const
0298 {
0299     return d->ui.prompt->text();
0300 }
0301 
0302 void KPasswordDialog::setPassword(const QString &p)
0303 {
0304     d->ui.passEdit->setPassword(p);
0305 }
0306 
0307 void KPasswordDialog::setUsernameReadOnly(bool readOnly)
0308 {
0309     d->ui.userEdit->setReadOnly(readOnly);
0310 
0311     if (readOnly && d->ui.userEdit->hasFocus()) {
0312         d->ui.passEdit->setFocus();
0313     }
0314 }
0315 
0316 void KPasswordDialog::setKnownLogins(const QMap<QString, QString> &knownLogins)
0317 {
0318     const int nr = knownLogins.count();
0319     if (nr == 0) {
0320         return;
0321     }
0322 
0323     if (nr == 1) {
0324         d->ui.userEdit->setText(knownLogins.begin().key());
0325         setPassword(knownLogins.begin().value());
0326         return;
0327     }
0328 
0329     Q_ASSERT(!d->ui.userEdit->isReadOnly());
0330     if (!d->userEditCombo) {
0331         int row = -1;
0332         QFormLayout::ItemRole userEditRole = QFormLayout::FieldRole;
0333 
0334         d->ui.formLayout->getWidgetPosition(d->ui.userEdit, &row, &userEditRole);
0335         d->ui.formLayout->removeWidget(d->ui.userEdit);
0336         delete d->ui.userEdit;
0337         d->userEditCombo = new QComboBox(d->ui.credentialsGroup);
0338         d->userEditCombo->setEditable(true);
0339         d->ui.userEdit = d->userEditCombo->lineEdit();
0340         d->ui.userNameLabel->setBuddy(d->userEditCombo);
0341         d->ui.formLayout->setWidget(row > -1 ? row : 0, userEditRole, d->userEditCombo);
0342 
0343         setTabOrder(d->ui.userEdit, d->ui.anonymousRadioButton);
0344         setTabOrder(d->ui.anonymousRadioButton, d->ui.domainEdit);
0345         setTabOrder(d->ui.domainEdit, d->ui.passEdit);
0346         setTabOrder(d->ui.passEdit, d->ui.keepCheckBox);
0347         connect(d->ui.userEdit, &QLineEdit::returnPressed, d->ui.passEdit, qOverload<>(&QWidget::setFocus));
0348     }
0349 
0350     d->knownLogins = knownLogins;
0351     d->userEditCombo->addItems(knownLogins.keys());
0352     d->userEditCombo->setFocus();
0353 
0354     connect(d->userEditCombo, &QComboBox::textActivated, this, [this](const QString &text) {
0355         d->activated(text);
0356     });
0357 }
0358 
0359 #if KWIDGETSADDONS_ENABLE_DEPRECATED_SINCE(6, 0)
0360 #pragma GCC diagnostic push
0361 #pragma GCC diagnostic ignored "-Wdeprecated-declarations"
0362 void KPasswordDialog::setRevealPasswordAvailable(bool reveal)
0363 {
0364     d->ui.passEdit->setRevealPasswordAvailable(reveal);
0365 }
0366 
0367 bool KPasswordDialog::isRevealPasswordAvailable() const
0368 {
0369     return d->ui.passEdit->isRevealPasswordAvailable();
0370 }
0371 #pragma GCC diagnostic pop
0372 #endif
0373 
0374 KPassword::RevealMode KPasswordDialog::revealPasswordMode() const
0375 {
0376     return d->ui.passEdit->revealPasswordMode();
0377 }
0378 
0379 void KPasswordDialog::setRevealPasswordMode(KPassword::RevealMode revealPasswordMode)
0380 {
0381     d->ui.passEdit->setRevealPasswordMode(revealPasswordMode);
0382 }
0383 
0384 void KPasswordDialogPrivate::activated(const QString &userName)
0385 {
0386     QMap<QString, QString>::ConstIterator it = knownLogins.constFind(userName);
0387     if (it != knownLogins.constEnd()) {
0388         q->setPassword(it.value());
0389     }
0390 }
0391 
0392 void KPasswordDialog::accept()
0393 {
0394     if (!d->ui.errorMessage->isHidden()) {
0395         d->ui.errorMessage->setText(QString());
0396     }
0397 
0398     // reset the font in case we had an error previously
0399     if (!d->ui.passwordLabel->isHidden()) {
0400         d->ui.passwordLabel->setFont(font());
0401         d->ui.userNameLabel->setFont(font());
0402     }
0403 
0404     // we do this to allow the error message, if any, to go away
0405     // checkPassword() may block for a period of time
0406     QTimer::singleShot(0, this, [this] {
0407         d->actuallyAccept();
0408     });
0409 }
0410 
0411 void KPasswordDialogPrivate::actuallyAccept()
0412 {
0413     if (!q->checkPassword()) {
0414         return;
0415     }
0416 
0417     bool keep = ui.keepCheckBox->isVisibleTo(q) && ui.keepCheckBox->isChecked();
0418     Q_EMIT q->gotPassword(q->password(), keep);
0419 
0420     if (ui.userEdit->isVisibleTo(q)) {
0421         Q_EMIT q->gotUsernameAndPassword(q->username(), q->password(), keep);
0422     }
0423 
0424     q->QDialog::accept();
0425 }
0426 
0427 bool KPasswordDialog::checkPassword()
0428 {
0429     return true;
0430 }
0431 
0432 QDialogButtonBox *KPasswordDialog::buttonBox() const
0433 {
0434     return d->ui.buttonBox;
0435 }
0436 
0437 void KPasswordDialog::setUsernameContextHelp(const QString &help)
0438 {
0439     d->ui.userEditContextHelpButton->setVisible(true);
0440     d->ui.userEdit->setWhatsThis(help);
0441 }
0442 
0443 #include "moc_kpassworddialog.cpp"