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"