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"