File indexing completed on 2024-06-23 05:14:04

0001 /* -*- mode: c++; c-basic-offset:4 -*-
0002     dialogs/lookupcertificatesdialog.cpp
0003 
0004     This file is part of Kleopatra, the KDE keymanager
0005     SPDX-FileCopyrightText: 2008 Klarälvdalens Datakonsult AB
0006 
0007     SPDX-License-Identifier: GPL-2.0-or-later
0008 */
0009 
0010 #include <config-kleopatra.h>
0011 
0012 #include "lookupcertificatesdialog.h"
0013 
0014 #include <view/keytreeview.h>
0015 
0016 #include <kleopatra_debug.h>
0017 
0018 #include <Libkleo/KeyListModel>
0019 
0020 #include <KConfigGroup>
0021 #include <KLocalizedString>
0022 #include <KMessageWidget>
0023 #include <KSeparator>
0024 #include <KSharedConfig>
0025 
0026 #include <QDialogButtonBox>
0027 #include <QGridLayout>
0028 #include <QLabel>
0029 #include <QLineEdit>
0030 #include <QPushButton>
0031 #include <QRegularExpression>
0032 #include <QRegularExpressionValidator>
0033 #include <QTreeView>
0034 #include <QVBoxLayout>
0035 
0036 #include <gpgme++/key.h>
0037 
0038 using namespace Kleo;
0039 using namespace Kleo::Dialogs;
0040 using namespace GpgME;
0041 
0042 class LookupCertificatesDialog::Private
0043 {
0044     friend class ::Kleo::Dialogs::LookupCertificatesDialog;
0045     LookupCertificatesDialog *const q;
0046 
0047 public:
0048     explicit Private(LookupCertificatesDialog *qq);
0049     ~Private();
0050 
0051 private:
0052     void slotSelectionChanged()
0053     {
0054         enableDisableWidgets();
0055     }
0056     void slotSearchTextChanged()
0057     {
0058         enableDisableWidgets();
0059     }
0060     void slotSearchClicked()
0061     {
0062         Q_EMIT q->searchTextChanged(searchText());
0063     }
0064     void slotDetailsClicked()
0065     {
0066         Q_ASSERT(q->selectedCertificates().size() == 1);
0067         Q_EMIT q->detailsRequested(q->selectedCertificates().front());
0068     }
0069     void slotSaveAsClicked()
0070     {
0071         Q_EMIT q->saveAsRequested(q->selectedCertificates());
0072     }
0073 
0074     void readConfig();
0075     void writeConfig();
0076     void enableDisableWidgets();
0077 
0078     QString searchText() const
0079     {
0080         return ui.findED->text().trimmed();
0081     }
0082 
0083     std::vector<Key> selectedCertificates() const
0084     {
0085         const QAbstractItemView *const view = ui.resultTV->view();
0086         if (!view) {
0087             return std::vector<Key>();
0088         }
0089         const auto *const model = dynamic_cast<KeyListModelInterface *>(view->model());
0090         Q_ASSERT(model);
0091         const QItemSelectionModel *const sm = view->selectionModel();
0092         Q_ASSERT(sm);
0093         return model->keys(sm->selectedRows());
0094     }
0095 
0096     int numSelectedCertificates() const
0097     {
0098         return ui.resultTV->selectedKeys().size();
0099     }
0100 
0101     QValidator *queryValidator();
0102     void updateQueryMode();
0103 
0104 private:
0105     QueryMode queryMode = AnyQuery;
0106     bool passive;
0107     QValidator *anyQueryValidator = nullptr;
0108     QValidator *emailQueryValidator = nullptr;
0109 
0110     struct Ui {
0111         QLabel *guidanceLabel;
0112         QLabel *findLB;
0113         QLineEdit *findED;
0114         QPushButton *findPB;
0115         Kleo::KeyTreeView *resultTV;
0116         QPushButton *selectAllPB;
0117         QPushButton *deselectAllPB;
0118         QPushButton *detailsPB;
0119         QPushButton *saveAsPB;
0120         KMessageWidget *messageWidget;
0121         QDialogButtonBox *buttonBox;
0122 
0123         void setupUi(QDialog *dialog)
0124         {
0125             auto verticalLayout = new QVBoxLayout{dialog};
0126             auto gridLayout = new QGridLayout{};
0127 
0128             int row = 0;
0129             guidanceLabel = new QLabel{dialog};
0130             gridLayout->addWidget(guidanceLabel, row, 0, 1, 3);
0131 
0132             row++;
0133             findLB = new QLabel{i18n("Find:"), dialog};
0134             gridLayout->addWidget(findLB, row, 0, 1, 1);
0135 
0136             findED = new QLineEdit{dialog};
0137             findLB->setBuddy(findED);
0138             gridLayout->addWidget(findED, row, 1, 1, 1);
0139 
0140             findPB = new QPushButton{i18n("Search"), dialog};
0141             findPB->setAutoDefault(false);
0142             gridLayout->addWidget(findPB, row, 2, 1, 1);
0143 
0144             row++;
0145             gridLayout->addWidget(new KSeparator{Qt::Horizontal, dialog}, row, 0, 1, 3);
0146 
0147             row++;
0148             resultTV = new Kleo::KeyTreeView(dialog);
0149             resultTV->setEnabled(true);
0150             resultTV->setMinimumSize(QSize(400, 0));
0151             gridLayout->addWidget(resultTV, row, 0, 1, 2);
0152 
0153             auto buttonLayout = new QVBoxLayout{};
0154 
0155             selectAllPB = new QPushButton{i18n("Select All"), dialog};
0156             selectAllPB->setEnabled(false);
0157             selectAllPB->setAutoDefault(false);
0158             buttonLayout->addWidget(selectAllPB);
0159 
0160             deselectAllPB = new QPushButton{i18n("Deselect All"), dialog};
0161             deselectAllPB->setEnabled(false);
0162             deselectAllPB->setAutoDefault(false);
0163             buttonLayout->addWidget(deselectAllPB);
0164 
0165             buttonLayout->addStretch();
0166 
0167             detailsPB = new QPushButton{i18n("Details..."), dialog};
0168             detailsPB->setEnabled(false);
0169             detailsPB->setAutoDefault(false);
0170             buttonLayout->addWidget(detailsPB);
0171 
0172             saveAsPB = new QPushButton{i18n("Save As..."), dialog};
0173             saveAsPB->setEnabled(false);
0174             saveAsPB->setAutoDefault(false);
0175             buttonLayout->addWidget(saveAsPB);
0176 
0177             gridLayout->addLayout(buttonLayout, row, 2, 1, 1);
0178 
0179             row++;
0180             messageWidget = new KMessageWidget{dialog};
0181             messageWidget->setMessageType(KMessageWidget::Information);
0182             messageWidget->setVisible(false);
0183             gridLayout->addWidget(messageWidget, row, 0, 1, 3);
0184 
0185             verticalLayout->addLayout(gridLayout);
0186 
0187             buttonBox = new QDialogButtonBox{dialog};
0188             buttonBox->setStandardButtons(QDialogButtonBox::Close | QDialogButtonBox::Save);
0189             verticalLayout->addWidget(buttonBox);
0190 
0191             QObject::connect(findED, SIGNAL(returnPressed()), findPB, SLOT(animateClick()));
0192             QObject::connect(buttonBox, SIGNAL(accepted()), dialog, SLOT(accept()));
0193             QObject::connect(buttonBox, SIGNAL(rejected()), dialog, SLOT(reject()));
0194             QObject::connect(findPB, SIGNAL(clicked()), dialog, SLOT(slotSearchClicked()));
0195             QObject::connect(detailsPB, SIGNAL(clicked()), dialog, SLOT(slotDetailsClicked()));
0196             QObject::connect(saveAsPB, SIGNAL(clicked()), dialog, SLOT(slotSaveAsClicked()));
0197             QObject::connect(findED, SIGNAL(textChanged(QString)), dialog, SLOT(slotSearchTextChanged()));
0198 
0199             QMetaObject::connectSlotsByName(dialog);
0200         }
0201 
0202         explicit Ui(LookupCertificatesDialog *q)
0203         {
0204             q->setWindowTitle(i18nc("@title:window", "Lookup on Server"));
0205             setupUi(q);
0206 
0207             saveAsPB->hide(); // ### not yet implemented in LookupCertificatesCommand
0208 
0209             findED->setClearButtonEnabled(true);
0210 
0211             resultTV->setFlatModel(AbstractKeyListModel::createFlatKeyListModel(q));
0212             resultTV->setHierarchicalView(false);
0213 
0214             importPB()->setText(i18n("Import"));
0215             importPB()->setEnabled(false);
0216 
0217             connect(resultTV->view(), SIGNAL(doubleClicked(QModelIndex)), importPB(), SLOT(animateClick()));
0218 
0219             findED->setFocus();
0220 
0221             connect(selectAllPB, &QPushButton::clicked, resultTV->view(), &QTreeView::selectAll);
0222             connect(deselectAllPB, &QPushButton::clicked, resultTV->view(), &QTreeView::clearSelection);
0223         }
0224 
0225         QPushButton *importPB() const
0226         {
0227             return buttonBox->button(QDialogButtonBox::Save);
0228         }
0229         QPushButton *closePB() const
0230         {
0231             return buttonBox->button(QDialogButtonBox::Close);
0232         }
0233     } ui;
0234 };
0235 
0236 LookupCertificatesDialog::Private::Private(LookupCertificatesDialog *qq)
0237     : q(qq)
0238     , passive(false)
0239     , ui(q)
0240 {
0241     connect(ui.resultTV->view()->selectionModel(), SIGNAL(selectionChanged(QItemSelection, QItemSelection)), q, SLOT(slotSelectionChanged()));
0242     updateQueryMode();
0243 }
0244 
0245 LookupCertificatesDialog::Private::~Private()
0246 {
0247 }
0248 
0249 void LookupCertificatesDialog::Private::readConfig()
0250 {
0251     KConfigGroup configGroup(KSharedConfig::openStateConfig(), QStringLiteral("LookupCertificatesDialog"));
0252 
0253     KConfigGroup resultKeysConfig = configGroup.group(QStringLiteral("ResultKeysView"));
0254     ui.resultTV->restoreLayout(resultKeysConfig);
0255 
0256     const QSize size = configGroup.readEntry("Size", QSize(600, 400));
0257     if (size.isValid()) {
0258         q->resize(size);
0259     }
0260 }
0261 
0262 void LookupCertificatesDialog::Private::writeConfig()
0263 {
0264     KConfigGroup configGroup(KSharedConfig::openStateConfig(), QStringLiteral("LookupCertificatesDialog"));
0265     configGroup.writeEntry("Size", q->size());
0266     configGroup.sync();
0267 }
0268 
0269 static QString guidanceText(LookupCertificatesDialog::QueryMode mode)
0270 {
0271     switch (mode) {
0272     default:
0273         qCWarning(KLEOPATRA_LOG) << __func__ << "Unknown query mode:" << mode;
0274         [[fallthrough]];
0275     case LookupCertificatesDialog::AnyQuery:
0276         return xi18nc("@info", "Enter a search term to search for matching certificates.");
0277     case LookupCertificatesDialog::EmailQuery:
0278         return xi18nc("@info", "Enter an email address to search for matching certificates.");
0279     };
0280 }
0281 
0282 QValidator *LookupCertificatesDialog::Private::queryValidator()
0283 {
0284     switch (queryMode) {
0285     default:
0286         qCWarning(KLEOPATRA_LOG) << __func__ << "Unknown query mode:" << queryMode;
0287         [[fallthrough]];
0288     case AnyQuery: {
0289         if (!anyQueryValidator) {
0290             // allow any query with at least one non-whitespace character
0291             anyQueryValidator = new QRegularExpressionValidator{QRegularExpression{QStringLiteral(".*\\S+.*")}, q};
0292         }
0293         return anyQueryValidator;
0294     }
0295     case EmailQuery: {
0296         if (!emailQueryValidator) {
0297             // allow anything that looks remotely like an email address, i.e.
0298             // anything with an '@' surrounded by non-whitespace characters
0299             const QRegularExpression simpleEmailRegex{QStringLiteral(".*\\S+@\\S+.*")};
0300             emailQueryValidator = new QRegularExpressionValidator{simpleEmailRegex, q};
0301         }
0302         return emailQueryValidator;
0303     }
0304     }
0305 }
0306 
0307 void LookupCertificatesDialog::Private::updateQueryMode()
0308 {
0309     ui.guidanceLabel->setText(guidanceText(queryMode));
0310     ui.findED->setValidator(queryValidator());
0311 }
0312 
0313 LookupCertificatesDialog::LookupCertificatesDialog(QWidget *p, Qt::WindowFlags f)
0314     : QDialog(p, f)
0315     , d(new Private(this))
0316 {
0317     d->ui.findPB->setEnabled(false);
0318     d->readConfig();
0319 }
0320 
0321 LookupCertificatesDialog::~LookupCertificatesDialog()
0322 {
0323     d->writeConfig();
0324 }
0325 
0326 void LookupCertificatesDialog::setQueryMode(QueryMode mode)
0327 {
0328     d->queryMode = mode;
0329     d->updateQueryMode();
0330 }
0331 
0332 LookupCertificatesDialog::QueryMode LookupCertificatesDialog::queryMode() const
0333 {
0334     return d->queryMode;
0335 }
0336 
0337 void LookupCertificatesDialog::setCertificates(const std::vector<Key> &certs)
0338 {
0339     d->ui.resultTV->view()->setFocus();
0340     d->ui.resultTV->setKeys(certs);
0341 }
0342 
0343 std::vector<Key> LookupCertificatesDialog::selectedCertificates() const
0344 {
0345     return d->selectedCertificates();
0346 }
0347 
0348 void LookupCertificatesDialog::setPassive(bool on)
0349 {
0350     if (d->passive == on) {
0351         return;
0352     }
0353     d->passive = on;
0354     d->enableDisableWidgets();
0355 }
0356 
0357 bool LookupCertificatesDialog::isPassive() const
0358 {
0359     return d->passive;
0360 }
0361 
0362 void LookupCertificatesDialog::setSearchText(const QString &text)
0363 {
0364     d->ui.findED->setText(text);
0365 }
0366 
0367 QString LookupCertificatesDialog::searchText() const
0368 {
0369     return d->ui.findED->text();
0370 }
0371 
0372 void LookupCertificatesDialog::showInformation(const QString &message)
0373 {
0374     d->ui.messageWidget->setText(message);
0375     if (message.isEmpty()) {
0376         d->ui.messageWidget->animatedHide();
0377     } else {
0378         d->ui.messageWidget->animatedShow();
0379     }
0380 }
0381 
0382 void LookupCertificatesDialog::accept()
0383 {
0384     Q_ASSERT(!selectedCertificates().empty());
0385     Q_EMIT importRequested(selectedCertificates());
0386     QDialog::accept();
0387 }
0388 
0389 void LookupCertificatesDialog::Private::enableDisableWidgets()
0390 {
0391     // enable/disable everything except 'close', based on passive:
0392     const QList<QObject *> list = q->children();
0393     for (QObject *const o : list) {
0394         if (QWidget *const w = qobject_cast<QWidget *>(o)) {
0395             w->setDisabled(passive && w != ui.closePB() && w != ui.buttonBox);
0396         }
0397     }
0398 
0399     if (passive) {
0400         return;
0401     }
0402 
0403     ui.findPB->setEnabled(ui.findED->hasAcceptableInput());
0404 
0405     const int n = q->selectedCertificates().size();
0406 
0407     ui.detailsPB->setEnabled(n == 1);
0408     ui.saveAsPB->setEnabled(n == 1);
0409     ui.importPB()->setEnabled(n != 0);
0410     ui.importPB()->setDefault(false); // otherwise Import becomes default button if enabled and return triggers both a search and accept()
0411 }
0412 
0413 #include "moc_lookupcertificatesdialog.cpp"