File indexing completed on 2024-04-28 15:32:10

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 #if KWIDGETSADDONS_BUILD_DEPRECATED_SINCE(5, 63)
0140 void KPasswordDialog::setPixmap(const QPixmap &pixmap)
0141 {
0142     d->ui.pixmapLabel->setPixmap(pixmap);
0143 }
0144 
0145 QPixmap KPasswordDialog::pixmap() const
0146 {
0147     return d->ui.pixmapLabel->pixmap(Qt::ReturnByValue);
0148 }
0149 #endif
0150 
0151 void KPasswordDialog::setUsername(const QString &user)
0152 {
0153     d->ui.userEdit->setText(user);
0154     if (user.isEmpty()) {
0155         return;
0156     }
0157 
0158     d->activated(user);
0159     if (d->ui.userEdit->isVisibleTo(this)) {
0160         d->ui.passEdit->setFocus();
0161     }
0162 }
0163 
0164 QString KPasswordDialog::username() const
0165 {
0166     return d->ui.userEdit->text();
0167 }
0168 
0169 QString KPasswordDialog::password() const
0170 {
0171     return d->ui.passEdit->password();
0172 }
0173 
0174 void KPasswordDialog::setDomain(const QString &domain)
0175 {
0176     d->ui.domainEdit->setText(domain);
0177 }
0178 
0179 QString KPasswordDialog::domain() const
0180 {
0181     return d->ui.domainEdit->text();
0182 }
0183 
0184 void KPasswordDialog::setAnonymousMode(bool anonymous)
0185 {
0186     if (anonymous && !(d->m_flags & KPasswordDialog::ShowAnonymousLoginCheckBox)) {
0187         // This is an error case, but we can at least let user see what's about
0188         // to happen if they proceed.
0189         d->ui.anonymousRadioButton->setVisible(true);
0190 
0191         d->ui.usePasswordButton->setVisible(true);
0192         d->ui.usePasswordButton->setEnabled(false);
0193     }
0194 
0195     d->ui.anonymousRadioButton->setChecked(anonymous);
0196 }
0197 
0198 bool KPasswordDialog::anonymousMode() const
0199 {
0200     return d->ui.anonymousRadioButton->isChecked();
0201 }
0202 
0203 void KPasswordDialog::setKeepPassword(bool b)
0204 {
0205     d->ui.keepCheckBox->setChecked(b);
0206 }
0207 
0208 bool KPasswordDialog::keepPassword() const
0209 {
0210     return d->ui.keepCheckBox->isChecked();
0211 }
0212 
0213 void KPasswordDialog::addCommentLine(const QString &label, const QString &comment)
0214 {
0215     int gridMarginLeft;
0216     int gridMarginTop;
0217     int gridMarginRight;
0218     int gridMarginBottom;
0219     d->ui.formLayout->getContentsMargins(&gridMarginLeft, &gridMarginTop, &gridMarginRight, &gridMarginBottom);
0220 
0221     int spacing = d->ui.formLayout->horizontalSpacing();
0222     if (spacing < 0) {
0223         // same inter-column spacing for all rows, see comment in qformlayout.cpp
0224         spacing = style()->combinedLayoutSpacing(QSizePolicy::Label, QSizePolicy::LineEdit, Qt::Horizontal, nullptr, this);
0225     }
0226 
0227     QLabel *c = new QLabel(comment, this);
0228     c->setWordWrap(true);
0229 
0230     d->ui.formLayout->insertRow(d->commentRow, label, c);
0231     ++d->commentRow;
0232 
0233     // cycle through column 0 widgets and see the max width so we can set the minimum height of
0234     // column 2 wordwrapable labels
0235     int firstColumnWidth = 0;
0236     for (int i = 0; i < d->ui.formLayout->rowCount(); ++i) {
0237         QLayoutItem *li = d->ui.formLayout->itemAt(i, QFormLayout::LabelRole);
0238         if (li) {
0239             QWidget *w = li->widget();
0240             if (w && !w->isHidden()) {
0241                 firstColumnWidth = qMax(firstColumnWidth, w->sizeHint().width());
0242             }
0243         }
0244     }
0245     for (int i = 0; i < d->ui.formLayout->rowCount(); ++i) {
0246         QLayoutItem *li = d->ui.formLayout->itemAt(i, QFormLayout::FieldRole);
0247         if (li) {
0248             QLabel *l = qobject_cast<QLabel *>(li->widget());
0249             if (l && l->wordWrap()) {
0250                 auto *style = this->style();
0251                 const int leftMargin = style->pixelMetric(QStyle::PM_LayoutLeftMargin);
0252                 const int rightMargin = style->pixelMetric(QStyle::PM_LayoutRightMargin);
0253                 int w = sizeHint().width() - firstColumnWidth - leftMargin - rightMargin - gridMarginLeft - gridMarginRight - spacing;
0254                 l->setMinimumSize(w, l->heightForWidth(w));
0255             }
0256         }
0257     }
0258 }
0259 
0260 void KPasswordDialog::showErrorMessage(const QString &message, const ErrorType type)
0261 {
0262     d->ui.errorMessage->setText(message, KTitleWidget::ErrorMessage);
0263 
0264     QFont bold = font();
0265     bold.setBold(true);
0266     switch (type) {
0267     case PasswordError:
0268         d->ui.passwordLabel->setFont(bold);
0269         d->ui.passEdit->clear();
0270         d->ui.passEdit->setFocus();
0271         break;
0272     case UsernameError:
0273         if (d->ui.userEdit->isVisibleTo(this)) {
0274             d->ui.userNameLabel->setFont(bold);
0275             d->ui.userEdit->setFocus();
0276         }
0277         break;
0278     case DomainError:
0279         if (d->ui.domainEdit->isVisibleTo(this)) {
0280             d->ui.domainLabel->setFont(bold);
0281             d->ui.domainEdit->setFocus();
0282         }
0283         break;
0284     case FatalError:
0285         d->ui.userNameLabel->setEnabled(false);
0286         d->ui.userEdit->setEnabled(false);
0287         d->ui.passwordLabel->setEnabled(false);
0288         d->ui.passEdit->setEnabled(false);
0289         d->ui.keepCheckBox->setEnabled(false);
0290         d->ui.buttonBox->button(QDialogButtonBox::Ok)->setEnabled(false);
0291         break;
0292     default:
0293         break;
0294     }
0295     adjustSize();
0296 }
0297 
0298 void KPasswordDialog::setPrompt(const QString &prompt)
0299 {
0300     d->ui.prompt->setText(prompt);
0301     d->ui.prompt->setWordWrap(true);
0302     auto *style = this->style();
0303     const int leftMarginHint = style->pixelMetric(QStyle::PM_LayoutLeftMargin);
0304     const int rightMarginHint = style->pixelMetric(QStyle::PM_LayoutRightMargin);
0305     d->ui.prompt->setMinimumHeight(d->ui.prompt->heightForWidth(width() - leftMarginHint - rightMarginHint));
0306 }
0307 
0308 QString KPasswordDialog::prompt() const
0309 {
0310     return d->ui.prompt->text();
0311 }
0312 
0313 void KPasswordDialog::setPassword(const QString &p)
0314 {
0315     d->ui.passEdit->setPassword(p);
0316 }
0317 
0318 void KPasswordDialog::setUsernameReadOnly(bool readOnly)
0319 {
0320     d->ui.userEdit->setReadOnly(readOnly);
0321 
0322     if (readOnly && d->ui.userEdit->hasFocus()) {
0323         d->ui.passEdit->setFocus();
0324     }
0325 }
0326 
0327 void KPasswordDialog::setKnownLogins(const QMap<QString, QString> &knownLogins)
0328 {
0329     const int nr = knownLogins.count();
0330     if (nr == 0) {
0331         return;
0332     }
0333 
0334     if (nr == 1) {
0335         d->ui.userEdit->setText(knownLogins.begin().key());
0336         setPassword(knownLogins.begin().value());
0337         return;
0338     }
0339 
0340     Q_ASSERT(!d->ui.userEdit->isReadOnly());
0341     if (!d->userEditCombo) {
0342         int row = -1;
0343         QFormLayout::ItemRole userEditRole = QFormLayout::FieldRole;
0344 
0345         d->ui.formLayout->getWidgetPosition(d->ui.userEdit, &row, &userEditRole);
0346         d->ui.formLayout->removeWidget(d->ui.userEdit);
0347         delete d->ui.userEdit;
0348         d->userEditCombo = new QComboBox(d->ui.credentialsGroup);
0349         d->userEditCombo->setEditable(true);
0350         d->ui.userEdit = d->userEditCombo->lineEdit();
0351         d->ui.userNameLabel->setBuddy(d->userEditCombo);
0352         d->ui.formLayout->setWidget(row > -1 ? row : 0, userEditRole, d->userEditCombo);
0353 
0354         setTabOrder(d->ui.userEdit, d->ui.anonymousRadioButton);
0355         setTabOrder(d->ui.anonymousRadioButton, d->ui.domainEdit);
0356         setTabOrder(d->ui.domainEdit, d->ui.passEdit);
0357         setTabOrder(d->ui.passEdit, d->ui.keepCheckBox);
0358         connect(d->ui.userEdit, &QLineEdit::returnPressed, d->ui.passEdit, qOverload<>(&QWidget::setFocus));
0359     }
0360 
0361     d->knownLogins = knownLogins;
0362     d->userEditCombo->addItems(knownLogins.keys());
0363     d->userEditCombo->setFocus();
0364 
0365     connect(d->userEditCombo, &QComboBox::textActivated, this, [this](const QString &text) {
0366         d->activated(text);
0367     });
0368 }
0369 
0370 void KPasswordDialog::setRevealPasswordAvailable(bool reveal)
0371 {
0372     d->ui.passEdit->setRevealPasswordAvailable(reveal);
0373 }
0374 
0375 bool KPasswordDialog::isRevealPasswordAvailable() const
0376 {
0377     return d->ui.passEdit->isRevealPasswordAvailable();
0378 }
0379 
0380 void KPasswordDialogPrivate::activated(const QString &userName)
0381 {
0382     QMap<QString, QString>::ConstIterator it = knownLogins.constFind(userName);
0383     if (it != knownLogins.constEnd()) {
0384         q->setPassword(it.value());
0385     }
0386 }
0387 
0388 void KPasswordDialog::accept()
0389 {
0390     if (!d->ui.errorMessage->isHidden()) {
0391         d->ui.errorMessage->setText(QString());
0392     }
0393 
0394     // reset the font in case we had an error previously
0395     if (!d->ui.passwordLabel->isHidden()) {
0396         d->ui.passwordLabel->setFont(font());
0397         d->ui.userNameLabel->setFont(font());
0398     }
0399 
0400     // we do this to allow the error message, if any, to go away
0401     // checkPassword() may block for a period of time
0402     QTimer::singleShot(0, this, [this] {
0403         d->actuallyAccept();
0404     });
0405 }
0406 
0407 void KPasswordDialogPrivate::actuallyAccept()
0408 {
0409     if (!q->checkPassword()) {
0410         return;
0411     }
0412 
0413     bool keep = ui.keepCheckBox->isVisibleTo(q) && ui.keepCheckBox->isChecked();
0414     Q_EMIT q->gotPassword(q->password(), keep);
0415 
0416     if (ui.userEdit->isVisibleTo(q)) {
0417         Q_EMIT q->gotUsernameAndPassword(q->username(), q->password(), keep);
0418     }
0419 
0420     q->QDialog::accept();
0421 }
0422 
0423 bool KPasswordDialog::checkPassword()
0424 {
0425     return true;
0426 }
0427 
0428 QDialogButtonBox *KPasswordDialog::buttonBox() const
0429 {
0430     return d->ui.buttonBox;
0431 }
0432 
0433 void KPasswordDialog::setUsernameContextHelp(const QString &help)
0434 {
0435     d->ui.userEditContextHelpButton->setVisible(true);
0436     d->ui.userEdit->setWhatsThis(help);
0437 }
0438 
0439 #include "moc_kpassworddialog.cpp"