File indexing completed on 2025-01-05 04:55:47

0001 /*
0002     cryptoconfigmodule.cpp
0003 
0004     This file is part of kgpgcertmanager
0005     SPDX-FileCopyrightText: 2004 Klarälvdalens Datakonsult AB
0006 
0007     SPDX-License-Identifier: GPL-2.0-or-later
0008 */
0009 
0010 #include <config-libkleo.h>
0011 
0012 #include "cryptoconfigmodule.h"
0013 
0014 #include "cryptoconfigentryreaderport_p.h"
0015 #include "cryptoconfigmodule_p.h"
0016 #include "directoryserviceswidget.h"
0017 #include "filenamerequester.h"
0018 
0019 #include <libkleo/compliance.h>
0020 #include <libkleo/formatting.h>
0021 #include <libkleo/gnupg.h>
0022 #include <libkleo/keyserverconfig.h>
0023 
0024 #include <kleo_ui_debug.h>
0025 
0026 #include <KLazyLocalizedString>
0027 #include <KLineEdit>
0028 #include <KLocalizedString>
0029 #include <KMessageBox>
0030 #include <KSeparator>
0031 
0032 #include <QGpgME/CryptoConfig>
0033 
0034 #include <QCheckBox>
0035 #include <QComboBox>
0036 #include <QDialogButtonBox>
0037 #include <QGridLayout>
0038 #include <QGroupBox>
0039 #include <QHBoxLayout>
0040 #include <QIcon>
0041 #include <QLabel>
0042 #include <QLayout>
0043 #include <QPushButton>
0044 #include <QRegularExpression>
0045 #include <QScreen>
0046 #include <QScrollArea>
0047 #include <QSpinBox>
0048 #include <QStyle>
0049 #include <QVBoxLayout>
0050 
0051 #include <gpgme.h>
0052 
0053 #include <array>
0054 #include <limits>
0055 #include <memory>
0056 #include <set>
0057 
0058 using namespace Kleo;
0059 
0060 namespace
0061 {
0062 
0063 class ScrollArea : public QScrollArea
0064 {
0065 public:
0066     explicit ScrollArea(QWidget *p)
0067         : QScrollArea(p)
0068     {
0069     }
0070     QSize sizeHint() const override
0071     {
0072         const QSize wsz = widget() ? widget()->sizeHint() : QSize();
0073         return {wsz.width() + style()->pixelMetric(QStyle::PM_ScrollBarExtent), QScrollArea::sizeHint().height()};
0074     }
0075 };
0076 
0077 }
0078 inline QIcon loadIcon(const QString &s)
0079 {
0080     QString ss = s;
0081     const static QRegularExpression reg(QRegularExpression(QLatin1StringView("[^a-zA-Z0-9_]")));
0082     return QIcon::fromTheme(ss.replace(reg, QStringLiteral("-")));
0083 }
0084 
0085 static unsigned int num_components_with_options(const QGpgME::CryptoConfig *config)
0086 {
0087     if (!config) {
0088         return 0;
0089     }
0090     const QStringList components = config->componentList();
0091     unsigned int result = 0;
0092     for (QStringList::const_iterator it = components.begin(); it != components.end(); ++it) {
0093         if (const QGpgME::CryptoConfigComponent *const comp = config->component(*it)) {
0094             if (!comp->groupList().empty()) {
0095                 ++result;
0096             }
0097         }
0098     }
0099     return result;
0100 }
0101 
0102 static KPageView::FaceType determineJanusFace(const QGpgME::CryptoConfig *config, Kleo::CryptoConfigModule::Layout layout, bool &ok)
0103 {
0104     ok = true;
0105     if (num_components_with_options(config) < 2) {
0106         ok = false;
0107         return KPageView::Plain;
0108     }
0109     switch (layout) {
0110     case CryptoConfigModule::LinearizedLayout:
0111         return KPageView::Plain;
0112     case CryptoConfigModule::TabbedLayout:
0113         return KPageView::Tabbed;
0114     case CryptoConfigModule::IconListLayout:
0115         return KPageView::List;
0116     }
0117     Q_ASSERT(!"we should never get here");
0118     return KPageView::List;
0119 }
0120 
0121 Kleo::CryptoConfigModule::CryptoConfigModule(QGpgME::CryptoConfig *config, QWidget *parent)
0122     : KPageWidget(parent)
0123     , mConfig(config)
0124 {
0125     init(IconListLayout);
0126 }
0127 
0128 Kleo::CryptoConfigModule::CryptoConfigModule(QGpgME::CryptoConfig *config, Layout layout, QWidget *parent)
0129     : KPageWidget(parent)
0130     , mConfig(config)
0131 {
0132     init(layout);
0133 }
0134 
0135 void Kleo::CryptoConfigModule::init(Layout layout)
0136 {
0137     if (QLayout *l = this->layout()) {
0138         l->setContentsMargins(0, 0, 0, 0);
0139     }
0140 
0141     QGpgME::CryptoConfig *const config = mConfig;
0142 
0143     bool configOK = false;
0144     const KPageView::FaceType type = determineJanusFace(config, layout, configOK);
0145 
0146     setFaceType(type);
0147 
0148     QVBoxLayout *vlay = nullptr;
0149     QWidget *vbox = nullptr;
0150 
0151     if (type == Plain) {
0152         QWidget *w = new QWidget(this);
0153         auto l = new QVBoxLayout(w);
0154         l->setContentsMargins(0, 0, 0, 0);
0155         auto s = new QScrollArea(w);
0156         s->setFrameStyle(QFrame::NoFrame);
0157         s->setSizePolicy(QSizePolicy::MinimumExpanding, QSizePolicy::Preferred);
0158         s->setWidgetResizable(true);
0159         l->addWidget(s);
0160         vbox = new QWidget(s->viewport());
0161         vlay = new QVBoxLayout(vbox);
0162         vlay->setContentsMargins(0, 0, 0, 0);
0163         s->setWidget(vbox);
0164         addPage(w, configOK ? QString() : i18n("GpgConf Error"));
0165     }
0166 
0167     const QStringList components = sortComponentList(config->componentList());
0168     for (QStringList::const_iterator it = components.begin(); it != components.end(); ++it) {
0169         // qCDebug(KLEO_UI_LOG) <<"Component" << (*it).toLocal8Bit() <<":";
0170         QGpgME::CryptoConfigComponent *comp = config->component(*it);
0171         Q_ASSERT(comp);
0172         if (comp->groupList().empty()) {
0173             continue;
0174         }
0175 
0176         std::unique_ptr<CryptoConfigComponentGUI> compGUI(new CryptoConfigComponentGUI(this, comp));
0177         compGUI->setObjectName(*it);
0178         // KJanusWidget doesn't seem to have iterators, so we store a copy...
0179         mComponentGUIs.append(compGUI.get());
0180 
0181         if (type == Plain) {
0182             QGroupBox *gb = new QGroupBox(comp->description(), vbox);
0183             (new QVBoxLayout(gb))->addWidget(compGUI.release());
0184             vlay->addWidget(gb);
0185         } else {
0186             vbox = new QWidget(this);
0187             vlay = new QVBoxLayout(vbox);
0188             vlay->setContentsMargins(0, 0, 0, 0);
0189             KPageWidgetItem *pageItem = new KPageWidgetItem(vbox, comp->description());
0190             if (type != Tabbed) {
0191                 pageItem->setIcon(loadIcon(comp->iconName()));
0192             }
0193             addPage(pageItem);
0194 
0195             QScrollArea *scrollArea = type == Tabbed ? new QScrollArea(vbox) : new ScrollArea(vbox);
0196             scrollArea->setSizePolicy(QSizePolicy::MinimumExpanding, QSizePolicy::Preferred);
0197             scrollArea->setWidgetResizable(true);
0198 
0199             vlay->addWidget(scrollArea);
0200             const QSize compGUISize = compGUI->sizeHint();
0201             scrollArea->setWidget(compGUI.release());
0202 
0203             // Set a nice startup size
0204             const int deskHeight = screen()->size().height();
0205             int dialogHeight;
0206             if (deskHeight > 1000) { // very big desktop ?
0207                 dialogHeight = 800;
0208             } else if (deskHeight > 650) { // big desktop ?
0209                 dialogHeight = 500;
0210             } else { // small (800x600, 640x480) desktop
0211                 dialogHeight = 400;
0212             }
0213             Q_ASSERT(scrollArea->widget());
0214             if (type != Tabbed) {
0215                 scrollArea->setMinimumHeight(qMin(compGUISize.height(), dialogHeight));
0216             }
0217         }
0218     }
0219     if (mComponentGUIs.empty()) {
0220         const QString msg = i18n(
0221             "The gpgconf tool used to provide the information "
0222             "for this dialog does not seem to be installed "
0223             "properly. It did not return any components. "
0224             "Try running \"%1\" on the command line for more "
0225             "information.",
0226             components.empty() ? QLatin1StringView("gpgconf --list-components") : QLatin1String("gpgconf --list-options gpg"));
0227         QLabel *label = new QLabel(msg, vbox);
0228         label->setWordWrap(true);
0229         label->setMinimumHeight(fontMetrics().lineSpacing() * 5);
0230         vlay->addWidget(label);
0231     }
0232 }
0233 
0234 namespace
0235 {
0236 template<typename Iterator>
0237 QStringList sortConfigEntries(const Iterator orderBegin, const Iterator orderEnd, const QStringList &entries)
0238 {
0239     // components sorting algorithm:
0240     // 1. components with predefined order (provided via orderBegin / orderEnd)
0241     // 2. other components sorted alphabetically
0242     QStringList result;
0243     QStringList others;
0244     for (auto it = orderBegin; it != orderEnd; ++it) {
0245         if (entries.contains(*it)) {
0246             result.append(*it);
0247         }
0248     }
0249     for (const auto &item : entries) {
0250         if (!result.contains(item)) {
0251             others.append(item);
0252         }
0253     }
0254     others.sort();
0255     result.append(others);
0256     return result;
0257 }
0258 } // namespace
0259 
0260 QStringList Kleo::CryptoConfigModule::sortComponentList(const QStringList &components)
0261 {
0262     static const std::array<QString, 6> order = {
0263         QStringLiteral("gpg"),
0264         QStringLiteral("gpgsm"),
0265         QStringLiteral("gpg-agent"),
0266         QStringLiteral("dirmngr"),
0267         QStringLiteral("pinentry"),
0268         QStringLiteral("scdaemon"),
0269     };
0270     return sortConfigEntries(order.begin(), order.end(), components);
0271 }
0272 
0273 QStringList Kleo::CryptoConfigModule::sortGroupList(const QString &moduleName, const QStringList &groups)
0274 {
0275     if (moduleName == QStringLiteral("gpg")) {
0276         static const std::array<QString, 4> order = {
0277             QStringLiteral("Keyserver"),
0278             QStringLiteral("Configuration"),
0279             QStringLiteral("Monitor"),
0280             QStringLiteral("Debug"),
0281         };
0282         return sortConfigEntries(order.begin(), order.end(), groups);
0283     } else if (moduleName == QStringLiteral("gpgsm")) {
0284         static const std::array<QString, 4> order = {
0285             QStringLiteral("Security"),
0286             QStringLiteral("Configuration"),
0287             QStringLiteral("Monitor"),
0288             QStringLiteral("Debug"),
0289         };
0290         return sortConfigEntries(order.begin(), order.end(), groups);
0291     } else if (moduleName == QStringLiteral("gpg-agent")) {
0292         static const std::array<QString, 5> order = {
0293             QStringLiteral("Security"),
0294             QStringLiteral("Passphrase policy"),
0295             QStringLiteral("Configuration"),
0296             QStringLiteral("Monitor"),
0297             QStringLiteral("Debug"),
0298         };
0299         return sortConfigEntries(order.begin(), order.end(), groups);
0300     } else if (moduleName == QStringLiteral("dirmngr")) {
0301         static const std::array<QString, 10> order = {
0302             QStringLiteral("Keyserver"),
0303             QStringLiteral("HTTP"),
0304             QStringLiteral("LDAP"),
0305             QStringLiteral("OCSP"),
0306             QStringLiteral("Tor"),
0307             QStringLiteral("Enforcement"),
0308             QStringLiteral("Configuration"),
0309             QStringLiteral("Format"),
0310             QStringLiteral("Monitor"),
0311             QStringLiteral("Debug"),
0312         };
0313         return sortConfigEntries(order.begin(), order.end(), groups);
0314     } else if (moduleName == QStringLiteral("scdaemon")) {
0315         static const std::array<QString, 4> order = {
0316             QStringLiteral("Monitor"),
0317             QStringLiteral("Configuration"),
0318             QStringLiteral("Security"),
0319             QStringLiteral("Debug"),
0320         };
0321         return sortConfigEntries(order.begin(), order.end(), groups);
0322     } else {
0323         qCDebug(KLEO_UI_LOG) << "Configuration groups order is not defined for " << moduleName;
0324         QStringList result(groups);
0325         result.sort();
0326         return result;
0327     }
0328 }
0329 
0330 bool Kleo::CryptoConfigModule::hasError() const
0331 {
0332     return mComponentGUIs.empty();
0333 }
0334 
0335 void Kleo::CryptoConfigModule::save()
0336 {
0337     bool changed = false;
0338     QList<CryptoConfigComponentGUI *>::Iterator it = mComponentGUIs.begin();
0339     for (; it != mComponentGUIs.end(); ++it) {
0340         if ((*it)->save()) {
0341             changed = true;
0342         }
0343     }
0344     if (changed) {
0345         mConfig->sync(true /*runtime*/);
0346     }
0347 }
0348 
0349 void Kleo::CryptoConfigModule::reset()
0350 {
0351     QList<CryptoConfigComponentGUI *>::Iterator it = mComponentGUIs.begin();
0352     for (; it != mComponentGUIs.end(); ++it) {
0353         (*it)->load();
0354     }
0355 }
0356 
0357 void Kleo::CryptoConfigModule::defaults()
0358 {
0359     QList<CryptoConfigComponentGUI *>::Iterator it = mComponentGUIs.begin();
0360     for (; it != mComponentGUIs.end(); ++it) {
0361         (*it)->defaults();
0362     }
0363 }
0364 
0365 void Kleo::CryptoConfigModule::cancel()
0366 {
0367     mConfig->clear();
0368 }
0369 
0370 ////
0371 
0372 namespace
0373 {
0374 bool offerEntryForConfiguration(QGpgME::CryptoConfigEntry *entry)
0375 {
0376     static const QRegularExpression entryPathGroupSegmentRegexp{QStringLiteral("/.*/")};
0377 
0378     static std::set<QString> entriesToExclude;
0379     if (entriesToExclude.empty()) {
0380         entriesToExclude.insert(QStringLiteral("gpg/keyserver"));
0381         if (engineIsVersion(2, 3, 5, GpgME::GpgConfEngine)
0382             || (engineIsVersion(2, 2, 34, GpgME::GpgConfEngine) && !engineIsVersion(2, 3, 0, GpgME::GpgConfEngine))) {
0383             // exclude for 2.2.{34,...} and 2.3.5+
0384             entriesToExclude.insert(QStringLiteral("gpgsm/keyserver"));
0385         }
0386     }
0387 
0388     const bool de_vs = DeVSCompliance::isActive();
0389     // Skip "dangerous" expert options if we are running in CO_DE_VS.
0390     // Otherwise, skip any options beyond "invisible" (== expert + 1) level.
0391     const auto maxEntryLevel = de_vs ? QGpgME::CryptoConfigEntry::Level_Advanced //
0392                                      : QGpgME::CryptoConfigEntry::Level_Expert + 1;
0393     // we ignore the group when looking up entries to exclude because entries
0394     // are uniquely identified by their name and their component
0395     const auto entryId = entry->path().replace(entryPathGroupSegmentRegexp, QLatin1StringView{"/"}).toLower();
0396     return (entry->level() <= maxEntryLevel) && (entriesToExclude.find(entryId) == entriesToExclude.end());
0397 }
0398 
0399 auto getGroupEntriesToOfferForConfiguration(QGpgME::CryptoConfigGroup *group)
0400 {
0401     std::vector<QGpgME::CryptoConfigEntry *> result;
0402     const auto entryNames = group->entryList();
0403     for (const auto &entryName : entryNames) {
0404         auto *const entry = group->entry(entryName);
0405         Q_ASSERT(entry);
0406         if (offerEntryForConfiguration(entry)) {
0407             result.push_back(entry);
0408         } else {
0409             qCDebug(KLEO_UI_LOG) << "entry" << entry->path() << "too advanced or excluded explicitly, skipping";
0410         }
0411     }
0412     return result;
0413 }
0414 }
0415 
0416 Kleo::CryptoConfigComponentGUI::CryptoConfigComponentGUI(CryptoConfigModule *module, QGpgME::CryptoConfigComponent *component, QWidget *parent)
0417     : QWidget(parent)
0418     , mComponent(component)
0419 {
0420     auto glay = new QGridLayout(this);
0421     const QStringList groups = module->sortGroupList(mComponent->name(), mComponent->groupList());
0422     if (groups.size() > 1) {
0423         glay->setColumnMinimumWidth(0, 30);
0424         for (QStringList::const_iterator it = groups.begin(), end = groups.end(); it != end; ++it) {
0425             QGpgME::CryptoConfigGroup *group = mComponent->group(*it);
0426             Q_ASSERT(group);
0427             if (!group) {
0428                 continue;
0429             }
0430             auto groupEntries = getGroupEntriesToOfferForConfiguration(group);
0431             if (groupEntries.size() == 0) {
0432                 // skip groups without entries to be offered in the UI
0433                 continue;
0434             }
0435             const QString title = group->description();
0436             auto hbox = new QHBoxLayout;
0437             hbox->addWidget(new QLabel{title.isEmpty() ? *it : title, this});
0438             hbox->addWidget(new KSeparator{Qt::Horizontal, this}, 1);
0439             const int row = glay->rowCount();
0440             glay->addLayout(hbox, row, 0, 1, 3);
0441             mGroupGUIs.append(new CryptoConfigGroupGUI(module, group, groupEntries, glay, this));
0442         }
0443     } else if (!groups.empty()) {
0444         auto *const group = mComponent->group(groups.front());
0445         auto groupEntries = getGroupEntriesToOfferForConfiguration(group);
0446         if (groupEntries.size() > 0) {
0447             mGroupGUIs.append(new CryptoConfigGroupGUI(module, group, groupEntries, glay, this));
0448         }
0449     }
0450     glay->setRowStretch(glay->rowCount(), 1);
0451 }
0452 
0453 bool Kleo::CryptoConfigComponentGUI::save()
0454 {
0455     bool changed = false;
0456     QList<CryptoConfigGroupGUI *>::Iterator it = mGroupGUIs.begin();
0457     for (; it != mGroupGUIs.end(); ++it) {
0458         if ((*it)->save()) {
0459             changed = true;
0460         }
0461     }
0462     return changed;
0463 }
0464 
0465 void Kleo::CryptoConfigComponentGUI::load()
0466 {
0467     QList<CryptoConfigGroupGUI *>::Iterator it = mGroupGUIs.begin();
0468     for (; it != mGroupGUIs.end(); ++it) {
0469         (*it)->load();
0470     }
0471 }
0472 
0473 void Kleo::CryptoConfigComponentGUI::defaults()
0474 {
0475     QList<CryptoConfigGroupGUI *>::Iterator it = mGroupGUIs.begin();
0476     for (; it != mGroupGUIs.end(); ++it) {
0477         (*it)->defaults();
0478     }
0479 }
0480 
0481 ////
0482 
0483 Kleo::CryptoConfigGroupGUI::CryptoConfigGroupGUI(CryptoConfigModule *module,
0484                                                  QGpgME::CryptoConfigGroup *group,
0485                                                  const std::vector<QGpgME::CryptoConfigEntry *> &entries,
0486                                                  QGridLayout *glay,
0487                                                  QWidget *widget)
0488     : QObject(module)
0489 {
0490     const int startRow = glay->rowCount();
0491     for (auto entry : entries) {
0492         CryptoConfigEntryGUI *entryGUI = CryptoConfigEntryGUIFactory::createEntryGUI(module, entry, entry->name(), glay, widget);
0493         if (entryGUI) {
0494             mEntryGUIs.append(entryGUI);
0495             entryGUI->load();
0496         }
0497     }
0498     const int endRow = glay->rowCount() - 1;
0499     if (endRow < startRow) {
0500         return;
0501     }
0502 
0503     const QString iconName = group->iconName();
0504     if (iconName.isEmpty()) {
0505         return;
0506     }
0507 
0508     QLabel *l = new QLabel(widget);
0509     l->setPixmap(loadIcon(iconName).pixmap(32, 32));
0510     glay->addWidget(l, startRow, 0, endRow - startRow + 1, 1, Qt::AlignTop);
0511 }
0512 
0513 bool Kleo::CryptoConfigGroupGUI::save()
0514 {
0515     bool changed = false;
0516     QList<CryptoConfigEntryGUI *>::Iterator it = mEntryGUIs.begin();
0517     for (; it != mEntryGUIs.end(); ++it) {
0518         if ((*it)->isChanged()) {
0519             (*it)->save();
0520             changed = true;
0521         }
0522     }
0523     return changed;
0524 }
0525 
0526 void Kleo::CryptoConfigGroupGUI::load()
0527 {
0528     QList<CryptoConfigEntryGUI *>::Iterator it = mEntryGUIs.begin();
0529     for (; it != mEntryGUIs.end(); ++it) {
0530         (*it)->load();
0531     }
0532 }
0533 
0534 void Kleo::CryptoConfigGroupGUI::defaults()
0535 {
0536     QList<CryptoConfigEntryGUI *>::Iterator it = mEntryGUIs.begin();
0537     for (; it != mEntryGUIs.end(); ++it) {
0538         (*it)->resetToDefault();
0539     }
0540 }
0541 
0542 ////
0543 
0544 using constructor = CryptoConfigEntryGUI *(*)(CryptoConfigModule *, QGpgME::CryptoConfigEntry *, const QString &, QGridLayout *, QWidget *);
0545 
0546 namespace
0547 {
0548 template<typename T_Widget>
0549 CryptoConfigEntryGUI *_create(CryptoConfigModule *m, QGpgME::CryptoConfigEntry *e, const QString &n, QGridLayout *l, QWidget *p)
0550 {
0551     return new T_Widget(m, e, n, l, p);
0552 }
0553 }
0554 
0555 static const struct WidgetsByEntryName {
0556     const char *entryGlob;
0557     constructor create;
0558 } widgetsByEntryName[] = {
0559     {"*/*/debug-level", &_create<CryptoConfigEntryDebugLevel>},
0560     {"scdaemon/*/reader-port", &_create<CryptoConfigEntryReaderPort>},
0561 };
0562 static const unsigned int numWidgetsByEntryName = sizeof widgetsByEntryName / sizeof *widgetsByEntryName;
0563 
0564 static const constructor listWidgets[QGpgME::CryptoConfigEntry::NumArgType] = {
0565     // None: A list of options with no arguments (e.g. -v -v -v) is shown as a spinbox
0566     &_create<CryptoConfigEntrySpinBox>,
0567     nullptr, // String
0568     // Int/UInt: Let people type list of numbers (1,2,3....). Untested.
0569     &_create<CryptoConfigEntryLineEdit>,
0570     &_create<CryptoConfigEntryLineEdit>,
0571     nullptr, // Path
0572     nullptr, // Formerly URL
0573     &_create<CryptoConfigEntryLDAPURL>,
0574     nullptr, // DirPath
0575 };
0576 
0577 static const constructor scalarWidgets[QGpgME::CryptoConfigEntry::NumArgType] = {
0578     // clang-format off
0579     &_create<CryptoConfigEntryCheckBox>, // None
0580     &_create<CryptoConfigEntryLineEdit>, // String
0581     &_create<CryptoConfigEntrySpinBox>,  // Int
0582     &_create<CryptoConfigEntrySpinBox>,  // UInt
0583     &_create<CryptoConfigEntryPath>,     // Path
0584     nullptr,                             // Formerly URL
0585     nullptr,                             // LDAPURL
0586     &_create<CryptoConfigEntryDirPath>,  // DirPath
0587     // clang-format on
0588 };
0589 
0590 CryptoConfigEntryGUI *Kleo::CryptoConfigEntryGUIFactory::createEntryGUI(CryptoConfigModule *module,
0591                                                                         QGpgME::CryptoConfigEntry *entry,
0592                                                                         const QString &entryName,
0593                                                                         QGridLayout *glay,
0594                                                                         QWidget *widget)
0595 {
0596     Q_ASSERT(entry);
0597 
0598     // try to lookup by path:
0599     const QString path = entry->path();
0600     for (unsigned int i = 0; i < numWidgetsByEntryName; ++i) {
0601         if (QRegularExpression::fromWildcard(QString::fromLatin1(widgetsByEntryName[i].entryGlob), Qt::CaseSensitive).match(path).hasMatch()) {
0602             return widgetsByEntryName[i].create(module, entry, entryName, glay, widget);
0603         }
0604     }
0605 
0606     // none found, so look up by type:
0607     const unsigned int argType = entry->argType();
0608     Q_ASSERT(argType < QGpgME::CryptoConfigEntry::NumArgType);
0609     if (entry->isList()) {
0610         if (const constructor create = listWidgets[argType]) {
0611             return create(module, entry, entryName, glay, widget);
0612         } else {
0613             qCWarning(KLEO_UI_LOG) << "No widget implemented for list of type" << entry->argType();
0614         }
0615     } else if (const constructor create = scalarWidgets[argType]) {
0616         return create(module, entry, entryName, glay, widget);
0617     } else {
0618         qCWarning(KLEO_UI_LOG) << "No widget implemented for type" << entry->argType();
0619     }
0620 
0621     return nullptr;
0622 }
0623 
0624 ////
0625 
0626 Kleo::CryptoConfigEntryGUI::CryptoConfigEntryGUI(CryptoConfigModule *module, QGpgME::CryptoConfigEntry *entry, const QString &entryName)
0627     : QObject(module)
0628     , mEntry(entry)
0629     , mName(entryName)
0630     , mChanged(false)
0631 {
0632     connect(this, &CryptoConfigEntryGUI::changed, module, &CryptoConfigModule::changed);
0633 }
0634 
0635 QString Kleo::CryptoConfigEntryGUI::description() const
0636 {
0637     QString descr = mEntry->description();
0638     if (descr.isEmpty()) { // happens for expert options
0639         // String does not need to be translated because the options itself
0640         // are also not translated
0641         return QStringLiteral("\"%1\"").arg(mName);
0642     }
0643     if (i18nc("Translate this to 'yes' or 'no' (use the English words!) "
0644               "depending on whether your language uses "
0645               "Sentence style capitalization in GUI labels (yes) or not (no). "
0646               "Context: We get some backend strings in that have the wrong "
0647               "capitalization (in English, at least) so we need to force the "
0648               "first character to upper-case. It is this behaviour you can "
0649               "control for your language with this translation.",
0650               "yes")
0651         == QLatin1StringView("yes")) {
0652         descr[0] = descr[0].toUpper();
0653     }
0654     return descr;
0655 }
0656 
0657 void Kleo::CryptoConfigEntryGUI::resetToDefault()
0658 {
0659     mEntry->resetToDefault();
0660     load();
0661 }
0662 
0663 ////
0664 
0665 Kleo::CryptoConfigEntryLineEdit::CryptoConfigEntryLineEdit(CryptoConfigModule *module,
0666                                                            QGpgME::CryptoConfigEntry *entry,
0667                                                            const QString &entryName,
0668                                                            QGridLayout *glay,
0669                                                            QWidget *widget)
0670     : CryptoConfigEntryGUI(module, entry, entryName)
0671 {
0672     const int row = glay->rowCount();
0673     mLineEdit = new KLineEdit(widget);
0674     QLabel *label = new QLabel(description(), widget);
0675     label->setBuddy(mLineEdit);
0676     glay->addWidget(label, row, 1);
0677     glay->addWidget(mLineEdit, row, 2);
0678     if (entry->isReadOnly()) {
0679         label->setEnabled(false);
0680         mLineEdit->setEnabled(false);
0681     } else {
0682         connect(mLineEdit, &KLineEdit::textChanged, this, &CryptoConfigEntryLineEdit::slotChanged);
0683     }
0684 }
0685 
0686 void Kleo::CryptoConfigEntryLineEdit::doSave()
0687 {
0688     mEntry->setStringValue(mLineEdit->text());
0689 }
0690 
0691 void Kleo::CryptoConfigEntryLineEdit::doLoad()
0692 {
0693     mLineEdit->setText(mEntry->stringValue());
0694 }
0695 
0696 ////
0697 /* Note: Do not use "guru" as debug level but use the value 10.  The
0698    former also enables the creation of hash dump files and thus leaves
0699    traces of plaintext on the disk.  */
0700 static const struct {
0701     const KLazyLocalizedString label;
0702     const char *name;
0703 } debugLevels[] = {
0704     {kli18n("0 - None"), "none"},
0705     {kli18n("1 - Basic"), "basic"},
0706     {kli18n("2 - Verbose"), "advanced"},
0707     {kli18n("3 - More Verbose"), "expert"},
0708     {kli18n("4 - All"), "10"},
0709 };
0710 static const unsigned int numDebugLevels = sizeof debugLevels / sizeof *debugLevels;
0711 
0712 Kleo::CryptoConfigEntryDebugLevel::CryptoConfigEntryDebugLevel(CryptoConfigModule *module,
0713                                                                QGpgME::CryptoConfigEntry *entry,
0714                                                                const QString &entryName,
0715                                                                QGridLayout *glay,
0716                                                                QWidget *widget)
0717     : CryptoConfigEntryGUI(module, entry, entryName)
0718     , mComboBox(new QComboBox(widget))
0719 {
0720     QLabel *label = new QLabel(i18n("Set the debugging level to"), widget);
0721     label->setBuddy(mComboBox);
0722 
0723     for (unsigned int i = 0; i < numDebugLevels; ++i) {
0724         mComboBox->addItem(KLocalizedString(debugLevels[i].label).toString());
0725     }
0726 
0727     if (entry->isReadOnly()) {
0728         label->setEnabled(false);
0729         mComboBox->setEnabled(false);
0730     } else {
0731         connect(mComboBox, &QComboBox::currentIndexChanged, this, &CryptoConfigEntryDebugLevel::slotChanged);
0732     }
0733 
0734     const int row = glay->rowCount();
0735     glay->addWidget(label, row, 1);
0736     glay->addWidget(mComboBox, row, 2);
0737 }
0738 
0739 void Kleo::CryptoConfigEntryDebugLevel::doSave()
0740 {
0741     const unsigned int idx = mComboBox->currentIndex();
0742     if (idx < numDebugLevels) {
0743         mEntry->setStringValue(QLatin1StringView(debugLevels[idx].name));
0744     } else {
0745         mEntry->setStringValue(QString());
0746     }
0747 }
0748 
0749 void Kleo::CryptoConfigEntryDebugLevel::doLoad()
0750 {
0751     const QString str = mEntry->stringValue();
0752     for (unsigned int i = 0; i < numDebugLevels; ++i) {
0753         if (str == QLatin1StringView(debugLevels[i].name)) {
0754             mComboBox->setCurrentIndex(i);
0755             return;
0756         }
0757     }
0758     mComboBox->setCurrentIndex(0);
0759 }
0760 
0761 ////
0762 
0763 Kleo::CryptoConfigEntryPath::CryptoConfigEntryPath(CryptoConfigModule *module,
0764                                                    QGpgME::CryptoConfigEntry *entry,
0765                                                    const QString &entryName,
0766                                                    QGridLayout *glay,
0767                                                    QWidget *widget)
0768     : CryptoConfigEntryGUI(module, entry, entryName)
0769     , mFileNameRequester(nullptr)
0770 {
0771     const int row = glay->rowCount();
0772     mFileNameRequester = new FileNameRequester(widget);
0773     mFileNameRequester->setExistingOnly(false);
0774     mFileNameRequester->setFilter(QDir::Files);
0775     QLabel *label = new QLabel(description(), widget);
0776     label->setBuddy(mFileNameRequester);
0777     glay->addWidget(label, row, 1);
0778     glay->addWidget(mFileNameRequester, row, 2);
0779     if (entry->isReadOnly()) {
0780         label->setEnabled(false);
0781         mFileNameRequester->setEnabled(false);
0782     } else {
0783         connect(mFileNameRequester, &FileNameRequester::fileNameChanged, this, &CryptoConfigEntryPath::slotChanged);
0784     }
0785 }
0786 
0787 void Kleo::CryptoConfigEntryPath::doSave()
0788 {
0789     mEntry->setURLValue(QUrl::fromLocalFile(mFileNameRequester->fileName()));
0790 }
0791 
0792 void Kleo::CryptoConfigEntryPath::doLoad()
0793 {
0794     if (mEntry->urlValue().isLocalFile()) {
0795         mFileNameRequester->setFileName(mEntry->urlValue().toLocalFile());
0796     } else {
0797         mFileNameRequester->setFileName(mEntry->urlValue().toString());
0798     }
0799 }
0800 
0801 ////
0802 
0803 Kleo::CryptoConfigEntryDirPath::CryptoConfigEntryDirPath(CryptoConfigModule *module,
0804                                                          QGpgME::CryptoConfigEntry *entry,
0805                                                          const QString &entryName,
0806                                                          QGridLayout *glay,
0807                                                          QWidget *widget)
0808     : CryptoConfigEntryGUI(module, entry, entryName)
0809     , mFileNameRequester(nullptr)
0810 {
0811     const int row = glay->rowCount();
0812     mFileNameRequester = new FileNameRequester(widget);
0813     mFileNameRequester->setExistingOnly(false);
0814     mFileNameRequester->setFilter(QDir::Dirs);
0815     QLabel *label = new QLabel(description(), widget);
0816     label->setBuddy(mFileNameRequester);
0817     glay->addWidget(label, row, 1);
0818     glay->addWidget(mFileNameRequester, row, 2);
0819     if (entry->isReadOnly()) {
0820         label->setEnabled(false);
0821         mFileNameRequester->setEnabled(false);
0822     } else {
0823         connect(mFileNameRequester, &FileNameRequester::fileNameChanged, this, &CryptoConfigEntryDirPath::slotChanged);
0824     }
0825 }
0826 
0827 void Kleo::CryptoConfigEntryDirPath::doSave()
0828 {
0829     mEntry->setURLValue(QUrl::fromLocalFile(mFileNameRequester->fileName()));
0830 }
0831 
0832 void Kleo::CryptoConfigEntryDirPath::doLoad()
0833 {
0834     mFileNameRequester->setFileName(mEntry->urlValue().toLocalFile());
0835 }
0836 
0837 ////
0838 
0839 Kleo::CryptoConfigEntrySpinBox::CryptoConfigEntrySpinBox(CryptoConfigModule *module,
0840                                                          QGpgME::CryptoConfigEntry *entry,
0841                                                          const QString &entryName,
0842                                                          QGridLayout *glay,
0843                                                          QWidget *widget)
0844     : CryptoConfigEntryGUI(module, entry, entryName)
0845 {
0846     if (entry->argType() == QGpgME::CryptoConfigEntry::ArgType_None && entry->isList()) {
0847         mKind = ListOfNone;
0848     } else if (entry->argType() == QGpgME::CryptoConfigEntry::ArgType_UInt) {
0849         mKind = UInt;
0850     } else {
0851         Q_ASSERT(entry->argType() == QGpgME::CryptoConfigEntry::ArgType_Int);
0852         mKind = Int;
0853     }
0854 
0855     const int row = glay->rowCount();
0856     mNumInput = new QSpinBox(widget);
0857     QLabel *label = new QLabel(description(), widget);
0858     label->setBuddy(mNumInput);
0859     glay->addWidget(label, row, 1);
0860     glay->addWidget(mNumInput, row, 2);
0861 
0862     if (entry->isReadOnly()) {
0863         label->setEnabled(false);
0864         mNumInput->setEnabled(false);
0865     } else {
0866         mNumInput->setMinimum(mKind == Int ? std::numeric_limits<int>::min() : 0);
0867         mNumInput->setMaximum(std::numeric_limits<int>::max());
0868         connect(mNumInput, &QSpinBox::valueChanged, this, &CryptoConfigEntrySpinBox::slotChanged);
0869     }
0870 }
0871 
0872 void Kleo::CryptoConfigEntrySpinBox::doSave()
0873 {
0874     int value = mNumInput->value();
0875     switch (mKind) {
0876     case ListOfNone:
0877         mEntry->setNumberOfTimesSet(value);
0878         break;
0879     case UInt:
0880         mEntry->setUIntValue(value);
0881         break;
0882     case Int:
0883         mEntry->setIntValue(value);
0884         break;
0885     }
0886 }
0887 
0888 void Kleo::CryptoConfigEntrySpinBox::doLoad()
0889 {
0890     int value = 0;
0891     switch (mKind) {
0892     case ListOfNone:
0893         value = mEntry->numberOfTimesSet();
0894         break;
0895     case UInt:
0896         value = mEntry->uintValue();
0897         break;
0898     case Int:
0899         value = mEntry->intValue();
0900         break;
0901     }
0902     mNumInput->setValue(value);
0903 }
0904 
0905 ////
0906 
0907 Kleo::CryptoConfigEntryCheckBox::CryptoConfigEntryCheckBox(CryptoConfigModule *module,
0908                                                            QGpgME::CryptoConfigEntry *entry,
0909                                                            const QString &entryName,
0910                                                            QGridLayout *glay,
0911                                                            QWidget *widget)
0912     : CryptoConfigEntryGUI(module, entry, entryName)
0913 {
0914     const int row = glay->rowCount();
0915     mCheckBox = new QCheckBox(widget);
0916     glay->addWidget(mCheckBox, row, 1, 1, 2);
0917     mCheckBox->setText(description());
0918     if (entry->isReadOnly()) {
0919         mCheckBox->setEnabled(false);
0920     } else {
0921         connect(mCheckBox, &QCheckBox::toggled, this, &CryptoConfigEntryCheckBox::slotChanged);
0922     }
0923 }
0924 
0925 void Kleo::CryptoConfigEntryCheckBox::doSave()
0926 {
0927     mEntry->setBoolValue(mCheckBox->isChecked());
0928 }
0929 
0930 void Kleo::CryptoConfigEntryCheckBox::doLoad()
0931 {
0932     mCheckBox->setChecked(mEntry->boolValue());
0933 }
0934 
0935 Kleo::CryptoConfigEntryLDAPURL::CryptoConfigEntryLDAPURL(CryptoConfigModule *module,
0936                                                          QGpgME::CryptoConfigEntry *entry,
0937                                                          const QString &entryName,
0938                                                          QGridLayout *glay,
0939                                                          QWidget *widget)
0940     : CryptoConfigEntryGUI(module, entry, entryName)
0941 {
0942     mLabel = new QLabel(widget);
0943     mPushButton = new QPushButton(entry->isReadOnly() ? i18n("Show...") : i18n("Edit..."), widget);
0944 
0945     const int row = glay->rowCount();
0946     QLabel *label = new QLabel(description(), widget);
0947     label->setBuddy(mPushButton);
0948     glay->addWidget(label, row, 1);
0949     auto hlay = new QHBoxLayout;
0950     glay->addLayout(hlay, row, 2);
0951     hlay->addWidget(mLabel, 1);
0952     hlay->addWidget(mPushButton);
0953 
0954     if (entry->isReadOnly()) {
0955         mLabel->setEnabled(false);
0956     }
0957     connect(mPushButton, &QPushButton::clicked, this, &CryptoConfigEntryLDAPURL::slotOpenDialog);
0958 }
0959 
0960 void Kleo::CryptoConfigEntryLDAPURL::doLoad()
0961 {
0962     setURLList(mEntry->urlValueList());
0963 }
0964 
0965 void Kleo::CryptoConfigEntryLDAPURL::doSave()
0966 {
0967     mEntry->setURLValueList(mURLList);
0968 }
0969 
0970 void prepareURLCfgDialog(QDialog *dialog, DirectoryServicesWidget *dirserv, bool readOnly)
0971 {
0972     QDialogButtonBox *buttonBox = new QDialogButtonBox(QDialogButtonBox::Ok, dialog);
0973 
0974     if (!readOnly) {
0975         buttonBox->addButton(QDialogButtonBox::Cancel);
0976         buttonBox->addButton(QDialogButtonBox::RestoreDefaults);
0977 
0978         QPushButton *defaultsBtn = buttonBox->button(QDialogButtonBox::RestoreDefaults);
0979 
0980         QObject::connect(defaultsBtn, &QPushButton::clicked, dirserv, &DirectoryServicesWidget::clear);
0981         QObject::connect(buttonBox, &QDialogButtonBox::rejected, dialog, &QDialog::reject);
0982     }
0983 
0984     QObject::connect(buttonBox, &QDialogButtonBox::accepted, dialog, &QDialog::accept);
0985 
0986     auto layout = new QVBoxLayout;
0987     layout->addWidget(dirserv);
0988     layout->addWidget(buttonBox);
0989     dialog->setLayout(layout);
0990 }
0991 
0992 void Kleo::CryptoConfigEntryLDAPURL::slotOpenDialog()
0993 {
0994     if (!gpgme_check_version("1.16.0")) {
0995         KMessageBox::error(mPushButton->parentWidget(),
0996                            i18n("Configuration of directory services is not possible "
0997                                 "because the used gpgme libraries are too old."),
0998                            i18n("Sorry"));
0999         return;
1000     }
1001 
1002     // I'm a bad boy and I do it all on the stack. Enough classes already :)
1003     // This is just a simple dialog around the directory-services-widget
1004     QDialog dialog(mPushButton->parentWidget());
1005     dialog.setWindowTitle(i18nc("@title:window", "Configure Directory Services"));
1006 
1007     auto dirserv = new DirectoryServicesWidget(&dialog);
1008 
1009     prepareURLCfgDialog(&dialog, dirserv, mEntry->isReadOnly());
1010 
1011     dirserv->setReadOnly(mEntry->isReadOnly());
1012 
1013     std::vector<KeyserverConfig> servers;
1014     std::transform(std::cbegin(mURLList), std::cend(mURLList), std::back_inserter(servers), [](const auto &url) {
1015         return KeyserverConfig::fromUrl(url);
1016     });
1017     dirserv->setKeyservers(servers);
1018 
1019     if (dialog.exec()) {
1020         QList<QUrl> urls;
1021         const auto servers = dirserv->keyservers();
1022         std::transform(std::begin(servers), std::end(servers), std::back_inserter(urls), [](const auto &server) {
1023             return server.toUrl();
1024         });
1025         setURLList(urls);
1026         slotChanged();
1027     }
1028 }
1029 
1030 void Kleo::CryptoConfigEntryLDAPURL::setURLList(const QList<QUrl> &urlList)
1031 {
1032     mURLList = urlList;
1033     if (mURLList.isEmpty()) {
1034         mLabel->setText(i18n("None configured"));
1035     } else {
1036         mLabel->setText(i18np("1 server configured", "%1 servers configured", mURLList.count()));
1037     }
1038 }
1039 
1040 #include "moc_cryptoconfigmodule_p.cpp"
1041 
1042 #include "moc_cryptoconfigmodule.cpp"