cryptoconfigmodule.cpp 31.5 KB
Newer Older
1 2 3 4 5 6 7
/*
    cryptoconfigmodule.cpp

    This file is part of kgpgcertmanager
    Copyright (c) 2004 Klar�vdalens Datakonsult AB

    Libkleopatra is free software; you can redistribute it and/or
8 9 10
    modify it under the terms of the GNU General Public License as
    published by the Free Software Foundation; either version 2 of the
    License, or (at your option) any later version.
11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38

    Libkleopatra is distributed in the hope that it will be useful,
    but WITHOUT ANY WARRANTY; without even the implied warranty of
    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
    General Public License for more details.

    You should have received a copy of the GNU General Public License
    along with this program; if not, write to the Free Software
    Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA

    In addition, as a special exception, the copyright holders give
    permission to link the code of this program with any edition of
    the Qt library by Trolltech AS, Norway (or with modified versions
    of Qt that use the same license as Qt), and distribute linked
    combinations including the two.  You must obey the GNU General
    Public License in all respects for all of the code used other than
    Qt.  If you modify this file, you may extend this exception to
    your version of the file, but you are not obligated to do so.  If
    you do not wish to do so, delete this exception statement from
    your version.
*/

#include "cryptoconfigmodule.h"
#include "cryptoconfigmodule_p.h"
#include "directoryserviceswidget.h"
#include "kdhorizontalline.h"
#include "filenamerequester.h"

39
#include <qgpgme/cryptoconfig.h>
40

41
#include <KLineEdit>
42 43
#include <KLocalizedString>
#include "kleo_ui_debug.h"
44
#include <utils/formatting.h>
Laurent Montel's avatar
Laurent Montel committed
45
#include <QIcon>
46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66
#include <QDialogButtonBox>

#include <QSpinBox>

#include <QApplication>
#include <QLabel>
#include <QLayout>
#include <QPushButton>
#include <QRegExp>
#include <QVBoxLayout>
#include <QList>
#include <QHBoxLayout>
#include <QGridLayout>
#include <QScrollArea>
#include <QDesktopWidget>
#include <QCheckBox>
#include <QStyle>
#include <QComboBox>
#include <QGroupBox>

#include <memory>
67
#include <limits>
68 69 70 71 72 73 74 75 76 77

using namespace Kleo;

namespace
{

class ScrollArea : public QScrollArea
{
public:
    explicit ScrollArea(QWidget *p) : QScrollArea(p) {}
78
    QSize sizeHint() const override
79 80 81 82 83 84 85 86 87 88 89 90 91
    {
        const QSize wsz = widget() ? widget()->sizeHint() : QSize();
        return QSize(wsz.width() + style()->pixelMetric(QStyle::PM_ScrollBarExtent), QScrollArea::sizeHint().height());
    }
};

}
inline QIcon loadIcon(const QString &s)
{
    QString ss = s;
    return QIcon::fromTheme(ss.replace(QRegExp(QLatin1String("[^a-zA-Z0-9_]")), QStringLiteral("-")));
}

92
static unsigned int num_components_with_options(const QGpgME::CryptoConfig *config)
93 94 95 96 97 98 99
{
    if (!config) {
        return 0;
    }
    const QStringList components = config->componentList();
    unsigned int result = 0;
    for (QStringList::const_iterator it = components.begin(); it != components.end(); ++it)
100
        if (const QGpgME::CryptoConfigComponent *const comp = config->component(*it))
101 102 103 104 105 106
            if (!comp->groupList().empty()) {
                ++result;
            }
    return result;
}

107
static KPageView::FaceType determineJanusFace(const QGpgME::CryptoConfig *config, Kleo::CryptoConfigModule::Layout layout, bool &ok)
108 109 110 111 112 113 114 115 116 117 118 119
{
    ok = true;
    if (num_components_with_options(config) < 2) {
        ok = false;
        return KPageView::Plain;
    }
    return
        layout == CryptoConfigModule::LinearizedLayout ? KPageView::Plain :
        layout == CryptoConfigModule::TabbedLayout     ? KPageView::Tabbed :
        /* else */                                       KPageView::List;
}

120
Kleo::CryptoConfigModule::CryptoConfigModule(QGpgME::CryptoConfig *config, QWidget *parent)
121 122 123 124 125
    : KPageWidget(parent), mConfig(config)
{
    init(IconListLayout);
}

126
Kleo::CryptoConfigModule::CryptoConfigModule(QGpgME::CryptoConfig *config, Layout layout, QWidget *parent)
127 128 129 130 131 132 133 134
    : KPageWidget(parent), mConfig(config)
{
    init(layout);
}

void Kleo::CryptoConfigModule::init(Layout layout)
{
    if (QLayout *l = this->layout()) {
Laurent Montel's avatar
Laurent Montel committed
135
        l->setContentsMargins(0, 0, 0, 0);
136 137
    }

138
    QGpgME::CryptoConfig *const config = mConfig;
139 140 141 142 143 144

    bool configOK = false;
    const KPageView::FaceType type = determineJanusFace(config, layout, configOK);

    setFaceType(type);

Laurent Montel's avatar
Laurent Montel committed
145 146
    QVBoxLayout *vlay = nullptr;
    QWidget *vbox = nullptr;
147 148 149 150

    if (type == Plain) {
        QWidget *w = new QWidget(this);
        QVBoxLayout *l = new QVBoxLayout(w);
Laurent Montel's avatar
Laurent Montel committed
151
        l->setContentsMargins(0, 0, 0, 0);
152 153 154 155 156 157 158
        QScrollArea *s = new QScrollArea(w);
        s->setFrameStyle(QFrame::NoFrame);
        s->setSizePolicy(QSizePolicy::MinimumExpanding, QSizePolicy::Preferred);
        s->setWidgetResizable(true);
        l->addWidget(s);
        vbox = new QWidget(s->viewport());
        vlay = new QVBoxLayout(vbox);
Laurent Montel's avatar
Laurent Montel committed
159
        vlay->setContentsMargins(0, 0, 0, 0);
160 161 162 163 164 165 166
        s->setWidget(vbox);
        addPage(w, configOK ? QString() : i18n("GpgConf Error"));
    }

    const QStringList components = config->componentList();
    for (QStringList::const_iterator it = components.begin(); it != components.end(); ++it) {
        //qCDebug(KLEO_UI_LOG) <<"Component" << (*it).toLocal8Bit() <<":";
167
        QGpgME::CryptoConfigComponent *comp = config->component(*it);
168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184
        Q_ASSERT(comp);
        if (comp->groupList().empty()) {
            continue;
        }

        std::unique_ptr<CryptoConfigComponentGUI> compGUI(new CryptoConfigComponentGUI(this, comp));
        compGUI->setObjectName(*it);
        // KJanusWidget doesn't seem to have iterators, so we store a copy...
        mComponentGUIs.append(compGUI.get());

        if (type == Plain) {
            QGroupBox *gb = new QGroupBox(comp->description(), vbox);
            (new QVBoxLayout(gb))->addWidget(compGUI.release());
            vlay->addWidget(gb);
        } else {
            vbox = new QWidget(this);
            vlay = new QVBoxLayout(vbox);
Laurent Montel's avatar
Laurent Montel committed
185
            vlay->setContentsMargins(0, 0, 0, 0);
186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209
            KPageWidgetItem *pageItem = new KPageWidgetItem(vbox, comp->description());
            if (type != Tabbed) {
                pageItem->setIcon(loadIcon(comp->iconName()));
            }
            addPage(pageItem);

            QScrollArea *scrollArea = type == Tabbed ? new QScrollArea(vbox) : new ScrollArea(vbox);
            scrollArea->setSizePolicy(QSizePolicy::MinimumExpanding, QSizePolicy::Preferred);
            scrollArea->setWidgetResizable(true);

            vlay->addWidget(scrollArea);
            const QSize compGUISize = compGUI->sizeHint();
            scrollArea->setWidget(compGUI.release());

            // Set a nice startup size
            const int deskHeight = QApplication::desktop()->height();
            int dialogHeight;
            if (deskHeight > 1000) { // very big desktop ?
                dialogHeight = 800;
            } else if (deskHeight > 650) { // big desktop ?
                dialogHeight = 500;
            } else { // small (800x600, 640x480) desktop
                dialogHeight = 400;
            }
Daniel Vrátil's avatar
Daniel Vrátil committed
210
            Q_ASSERT(scrollArea->widget());
211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272
            if (type != Tabbed) {
                scrollArea->setMinimumHeight(qMin(compGUISize.height(), dialogHeight));
            }
        }
    }
    if (mComponentGUIs.empty()) {
        const QString msg = i18n("The gpgconf tool used to provide the information "
                                 "for this dialog does not seem to be installed "
                                 "properly. It did not return any components. "
                                 "Try running \"%1\" on the command line for more "
                                 "information.",
                                 components.empty() ? QLatin1String("gpgconf --list-components") : QLatin1String("gpgconf --list-options gpg"));
        QLabel *label = new QLabel(msg, vbox);
        label->setWordWrap(true);
        label->setMinimumHeight(fontMetrics().lineSpacing() * 5);
        vlay->addWidget(label);
    }
}

bool Kleo::CryptoConfigModule::hasError() const
{
    return mComponentGUIs.empty();
}

void Kleo::CryptoConfigModule::save()
{
    bool changed = false;
    QList<CryptoConfigComponentGUI *>::Iterator it = mComponentGUIs.begin();
    for (; it != mComponentGUIs.end(); ++it) {
        if ((*it)->save()) {
            changed = true;
        }
    }
    if (changed) {
        mConfig->sync(true /*runtime*/);
    }
}

void Kleo::CryptoConfigModule::reset()
{
    QList<CryptoConfigComponentGUI *>::Iterator it = mComponentGUIs.begin();
    for (; it != mComponentGUIs.end(); ++it) {
        (*it)->load();
    }
}

void Kleo::CryptoConfigModule::defaults()
{
    QList<CryptoConfigComponentGUI *>::Iterator it = mComponentGUIs.begin();
    for (; it != mComponentGUIs.end(); ++it) {
        (*it)->defaults();
    }
}

void Kleo::CryptoConfigModule::cancel()
{
    mConfig->clear();
}

////

Kleo::CryptoConfigComponentGUI::CryptoConfigComponentGUI(
273
    CryptoConfigModule *module, QGpgME::CryptoConfigComponent *component,
274 275 276 277 278 279 280 281 282
    QWidget *parent)
    : QWidget(parent),
      mComponent(component)
{
    QGridLayout *glay = new QGridLayout(this);
    const QStringList groups = mComponent->groupList();
    if (groups.size() > 1) {
        glay->setColumnMinimumWidth(0, KDHorizontalLine::indentHint());
        for (QStringList::const_iterator it = groups.begin(), end = groups.end(); it != end; ++it) {
283
            QGpgME::CryptoConfigGroup *group = mComponent->group(*it);
284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330
            Q_ASSERT(group);
            if (!group) {
                continue;
            }
            const QString title = group->description();
            KDHorizontalLine *hl = new KDHorizontalLine(title.isEmpty() ? *it : title, this);
            const int row = glay->rowCount();
            glay->addWidget(hl, row, 0, 1, 3);
            mGroupGUIs.append(new CryptoConfigGroupGUI(module, group, glay, this));
        }
    } else if (!groups.empty()) {
        mGroupGUIs.append(new CryptoConfigGroupGUI(module, mComponent->group(groups.front()), glay, this));
    }
    glay->setRowStretch(glay->rowCount(), 1);
}

bool Kleo::CryptoConfigComponentGUI::save()
{
    bool changed = false;
    QList<CryptoConfigGroupGUI *>::Iterator it = mGroupGUIs.begin();
    for (; it != mGroupGUIs.end(); ++it) {
        if ((*it)->save()) {
            changed = true;
        }
    }
    return changed;
}

void Kleo::CryptoConfigComponentGUI::load()
{
    QList<CryptoConfigGroupGUI *>::Iterator it = mGroupGUIs.begin();
    for (; it != mGroupGUIs.end(); ++it) {
        (*it)->load();
    }
}

void Kleo::CryptoConfigComponentGUI::defaults()
{
    QList<CryptoConfigGroupGUI *>::Iterator it = mGroupGUIs.begin();
    for (; it != mGroupGUIs.end(); ++it) {
        (*it)->defaults();
    }
}

////

Kleo::CryptoConfigGroupGUI::CryptoConfigGroupGUI(
331
    CryptoConfigModule *module, QGpgME::CryptoConfigGroup *group,
332 333 334
    QGridLayout *glay, QWidget *widget)
    : QObject(module), mGroup(group)
{
335
    const bool de_vs = Kleo::Formatting::complianceMode() == QLatin1String("de-vs");
336 337 338
    const int startRow = glay->rowCount();
    const QStringList entries = mGroup->entryList();
    for (QStringList::const_iterator it = entries.begin(), end = entries.end(); it != end; ++it) {
339
        QGpgME::CryptoConfigEntry *entry = group->entry(*it);
340
        Q_ASSERT(entry);
341 342 343 344 345
        /* Skip "dangerous" options if we are running in CO_DE_VS.  */
        if (de_vs && entry->level() > QGpgME::CryptoConfigEntry::Level_Advanced) {
            qCDebug(KLEO_UI_LOG) << "entry" << *it << "too advanced, skipping";
            continue;
        }
346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363
        CryptoConfigEntryGUI *entryGUI =
            CryptoConfigEntryGUIFactory::createEntryGUI(module, entry, *it, glay, widget);
        if (entryGUI) {
            mEntryGUIs.append(entryGUI);
            entryGUI->load();
        }
    }
    const int endRow = glay->rowCount() - 1;
    if (endRow < startRow) {
        return;
    }

    const QString iconName = group->iconName();
    if (iconName.isEmpty()) {
        return;
    }

    QLabel *l = new QLabel(widget);
364
    l->setPixmap(loadIcon(iconName).pixmap(32, 32));
365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 398
    glay->addWidget(l, startRow, 0, endRow - startRow + 1, 1, Qt::AlignTop);
}

bool Kleo::CryptoConfigGroupGUI::save()
{
    bool changed = false;
    QList<CryptoConfigEntryGUI *>::Iterator it = mEntryGUIs.begin();
    for (; it != mEntryGUIs.end(); ++it) {
        if ((*it)->isChanged()) {
            (*it)->save();
            changed = true;
        }
    }
    return changed;
}

void Kleo::CryptoConfigGroupGUI::load()
{
    QList<CryptoConfigEntryGUI *>::Iterator it = mEntryGUIs.begin();
    for (; it != mEntryGUIs.end(); ++it) {
        (*it)->load();
    }
}

void Kleo::CryptoConfigGroupGUI::defaults()
{
    QList<CryptoConfigEntryGUI *>::Iterator it = mEntryGUIs.begin();
    for (; it != mEntryGUIs.end(); ++it) {
        (*it)->resetToDefault();
    }
}

////

399
typedef CryptoConfigEntryGUI *(*constructor)(CryptoConfigModule *, QGpgME::CryptoConfigEntry *, const QString &, QGridLayout *, QWidget *);
400 401 402 403

namespace
{
template <typename T_Widget>
404
CryptoConfigEntryGUI *_create(CryptoConfigModule *m, QGpgME::CryptoConfigEntry *e, const QString &n, QGridLayout *l, QWidget *p)
405 406 407 408 409 410 411 412 413 414 415 416 417 418
{
    return new T_Widget(m, e, n, l, p);
}
}

static const struct WidgetsByEntryName {
    const char *entryGlob;
    constructor create;
} widgetsByEntryName[] = {
    { "*/*/debug-level",   &_create<CryptoConfigEntryDebugLevel> },
    { "gpg/*/keyserver",   &_create<CryptoConfigEntryKeyserver>  }
};
static const unsigned int numWidgetsByEntryName = sizeof widgetsByEntryName / sizeof * widgetsByEntryName;

419
static const constructor listWidgets[QGpgME::CryptoConfigEntry::NumArgType] = {
420 421
    // None: A list of options with no arguments (e.g. -v -v -v) is shown as a spinbox
    &_create<CryptoConfigEntrySpinBox>,
Laurent Montel's avatar
Laurent Montel committed
422
    nullptr, // String
423 424 425
    // Int/UInt: Let people type list of numbers (1,2,3....). Untested.
    &_create<CryptoConfigEntryLineEdit>,
    &_create<CryptoConfigEntryLineEdit>,
Laurent Montel's avatar
Laurent Montel committed
426 427
    nullptr, // Path
    nullptr, // Formerly URL
428
    &_create<CryptoConfigEntryLDAPURL>,
Laurent Montel's avatar
Laurent Montel committed
429
    nullptr, // DirPath
430 431
};

432
static const constructor scalarWidgets[QGpgME::CryptoConfigEntry::NumArgType] = {
433 434 435 436 437
    &_create<CryptoConfigEntryCheckBox>, // None
    &_create<CryptoConfigEntryLineEdit>, // String
    &_create<CryptoConfigEntrySpinBox>,  // Int
    &_create<CryptoConfigEntrySpinBox>,  // UInt
    &_create<CryptoConfigEntryPath>,     // Path
Laurent Montel's avatar
Laurent Montel committed
438 439
    nullptr,                                   // Formerly URL
    nullptr,                                   // LDAPURL
440 441 442
    &_create<CryptoConfigEntryDirPath>,  // DirPath
};

443
CryptoConfigEntryGUI *Kleo::CryptoConfigEntryGUIFactory::createEntryGUI(CryptoConfigModule *module, QGpgME::CryptoConfigEntry *entry, const QString &entryName, QGridLayout *glay, QWidget *widget)
444
{
Daniel Vrátil's avatar
Daniel Vrátil committed
445
    Q_ASSERT(entry);
446 447 448 449 450 451 452 453 454 455

    // try to lookup by path:
    const QString path = entry->path();
    for (unsigned int i = 0; i < numWidgetsByEntryName; ++i)
        if (QRegExp(QLatin1String(widgetsByEntryName[i].entryGlob), Qt::CaseSensitive, QRegExp::Wildcard).exactMatch(path)) {
            return widgetsByEntryName[i].create(module, entry, entryName, glay, widget);
        }

    // none found, so look up by type:
    const unsigned int argType = entry->argType();
Daniel Vrátil's avatar
Daniel Vrátil committed
456
    Q_ASSERT(argType < QGpgME::CryptoConfigEntry::NumArgType);
457 458 459 460 461 462 463 464 465 466 467 468
    if (entry->isList())
        if (const constructor create = listWidgets[argType]) {
            return create(module, entry, entryName, glay, widget);
        } else {
            qCWarning(KLEO_UI_LOG) << "No widget implemented for list of type" << entry->argType();
        }
    else if (const constructor create = scalarWidgets[argType]) {
        return create(module, entry, entryName, glay, widget);
    } else {
        qCWarning(KLEO_UI_LOG) << "No widget implemented for type" << entry->argType();
    }

Laurent Montel's avatar
Laurent Montel committed
469
    return nullptr;
470 471 472 473 474 475
}

////

Kleo::CryptoConfigEntryGUI::CryptoConfigEntryGUI(
    CryptoConfigModule *module,
476
    QGpgME::CryptoConfigEntry *entry,
477 478 479 480 481 482 483 484 485
    const QString &entryName)
    : QObject(module), mEntry(entry), mName(entryName), mChanged(false)
{
    connect(this, &CryptoConfigEntryGUI::changed, module, &CryptoConfigModule::changed);
}

QString Kleo::CryptoConfigEntryGUI::description() const
{
    QString descr = mEntry->description();
486 487 488 489
    if (descr.isEmpty()) { // happens for expert options
        // String does not need to be translated because the options itself
        // are also not translated
        return QStringLiteral("\"%1\"").arg(mName);
490 491 492
    }
    if (i18nc("Translate this to 'yes' or 'no' (use the English words!) "
              "depending on whether your language uses "
493
              "Sentence style capitalization in GUI labels (yes) or not (no). "
494
              "Context: We get some backend strings in that have the wrong "
495
              "capitalization (in English, at least) so we need to force the "
496 497 498 499 500 501 502 503 504 505 506 507 508 509 510 511 512
              "first character to upper-case. It is this behaviour you can "
              "control for your language with this translation.", "yes") == QLatin1String("yes")) {
        descr[0] = descr[0].toUpper();
    }
    return descr;
}

void Kleo::CryptoConfigEntryGUI::resetToDefault()
{
    mEntry->resetToDefault();
    load();
}

////

Kleo::CryptoConfigEntryLineEdit::CryptoConfigEntryLineEdit(
    CryptoConfigModule *module,
513
    QGpgME::CryptoConfigEntry *entry, const QString &entryName,
514 515 516 517 518 519 520 521 522 523 524 525 526 527 528 529 530 531 532 533 534 535 536 537 538 539 540 541 542 543 544 545 546
    QGridLayout *glay, QWidget *widget)
    : CryptoConfigEntryGUI(module, entry, entryName)
{
    const int row = glay->rowCount();
    mLineEdit = new KLineEdit(widget);
    QLabel *label = new QLabel(description(), widget);
    label->setBuddy(mLineEdit);
    glay->addWidget(label, row, 1);
    glay->addWidget(mLineEdit, row, 2);
    if (entry->isReadOnly()) {
        label->setEnabled(false);
        mLineEdit->setEnabled(false);
    } else {
        connect(mLineEdit, &KLineEdit::textChanged, this, &CryptoConfigEntryLineEdit::slotChanged);
    }
}

void Kleo::CryptoConfigEntryLineEdit::doSave()
{
    mEntry->setStringValue(mLineEdit->text());
}

void Kleo::CryptoConfigEntryLineEdit::doLoad()
{
    mLineEdit->setText(mEntry->stringValue());
}

////

static const struct {
    const char *label;
    const char *name;
} debugLevels[] = {
Andre Heinecke's avatar
Andre Heinecke committed
547 548 549 550 551
    { I18N_NOOP("0 - None"), "none"},
    { I18N_NOOP("1 - Basic"), "basic"},
    { I18N_NOOP("2 - Verbose"), "advanced"},
    { I18N_NOOP("3 - More Verbose"), "expert"},
    { I18N_NOOP("4 - All"), "guru"},
552 553 554
};
static const unsigned int numDebugLevels = sizeof debugLevels / sizeof * debugLevels;

555
Kleo::CryptoConfigEntryDebugLevel::CryptoConfigEntryDebugLevel(CryptoConfigModule *module, QGpgME::CryptoConfigEntry *entry,
556 557 558 559 560 561 562 563 564 565 566 567 568 569 570
        const QString &entryName, QGridLayout *glay, QWidget *widget)
    : CryptoConfigEntryGUI(module, entry, entryName),
      mComboBox(new QComboBox(widget))
{
    QLabel *label = new QLabel(i18n("Set the debugging level to"), widget);
    label->setBuddy(mComboBox);

    for (unsigned int i = 0; i < numDebugLevels; ++i) {
        mComboBox->addItem(i18n(debugLevels[i].label));
    }

    if (entry->isReadOnly()) {
        label->setEnabled(false);
        mComboBox->setEnabled(false);
    } else {
Laurent Montel's avatar
Laurent Montel committed
571
        connect(mComboBox, QOverload<int>::of(&QComboBox::currentIndexChanged),
572
                this, &CryptoConfigEntryDebugLevel::slotChanged);
573 574 575 576 577 578 579 580 581 582 583 584 585 586 587 588 589 590 591 592 593 594 595 596 597 598 599 600 601 602 603 604
    }

    const int row = glay->rowCount();
    glay->addWidget(label, row, 1);
    glay->addWidget(mComboBox, row, 2);
}

void Kleo::CryptoConfigEntryDebugLevel::doSave()
{
    const unsigned int idx = mComboBox->currentIndex();
    if (idx < numDebugLevels) {
        mEntry->setStringValue(QLatin1String(debugLevels[idx].name));
    } else {
        mEntry->setStringValue(QString());
    }
}

void Kleo::CryptoConfigEntryDebugLevel::doLoad()
{
    const QString str = mEntry->stringValue();
    for (unsigned int i = 0; i < numDebugLevels; ++i)
        if (str == QLatin1String(debugLevels[i].name)) {
            mComboBox->setCurrentIndex(i);
            return;
        }
    mComboBox->setCurrentIndex(0);
}

////

Kleo::CryptoConfigEntryPath::CryptoConfigEntryPath(
    CryptoConfigModule *module,
605
    QGpgME::CryptoConfigEntry *entry, const QString &entryName,
606 607
    QGridLayout *glay, QWidget *widget)
    : CryptoConfigEntryGUI(module, entry, entryName),
Laurent Montel's avatar
Laurent Montel committed
608
      mFileNameRequester(nullptr)
609 610
{
    const int row = glay->rowCount();
Laurent Montel's avatar
Laurent Montel committed
611
    mFileNameRequester = new FileNameRequester(widget);
612 613 614
    mFileNameRequester->setExistingOnly(false);
    mFileNameRequester->setFilter(QDir::Files);
    QLabel *label = new QLabel(description(), widget);
Laurent Montel's avatar
Laurent Montel committed
615
    label->setBuddy(mFileNameRequester);
616
    glay->addWidget(label, row, 1);
Laurent Montel's avatar
Laurent Montel committed
617
    glay->addWidget(mFileNameRequester, row, 2);
618 619
    if (entry->isReadOnly()) {
        label->setEnabled(false);
Laurent Montel's avatar
Laurent Montel committed
620
        mFileNameRequester->setEnabled(false);
621
    } else {
Laurent Montel's avatar
Laurent Montel committed
622
        connect(mFileNameRequester, &FileNameRequester::fileNameChanged, this, &CryptoConfigEntryPath::slotChanged);
623 624 625 626 627 628 629 630 631 632 633 634 635 636 637 638 639 640 641 642 643
    }
}

void Kleo::CryptoConfigEntryPath::doSave()
{
    mEntry->setURLValue(QUrl::fromLocalFile(mFileNameRequester->fileName()));
}

void Kleo::CryptoConfigEntryPath::doLoad()
{
    if (mEntry->urlValue().isLocalFile()) {
        mFileNameRequester->setFileName(mEntry->urlValue().toLocalFile());
    } else {
        mFileNameRequester->setFileName(mEntry->urlValue().toString());
    }
}

////

Kleo::CryptoConfigEntryDirPath::CryptoConfigEntryDirPath(
    CryptoConfigModule *module,
644
    QGpgME::CryptoConfigEntry *entry, const QString &entryName,
645 646
    QGridLayout *glay, QWidget *widget)
    : CryptoConfigEntryGUI(module, entry, entryName),
Laurent Montel's avatar
Laurent Montel committed
647
      mFileNameRequester(nullptr)
648 649
{
    const int row = glay->rowCount();
Laurent Montel's avatar
Laurent Montel committed
650
    mFileNameRequester = new FileNameRequester(widget);
651 652 653
    mFileNameRequester->setExistingOnly(false);
    mFileNameRequester->setFilter(QDir::Dirs);
    QLabel *label = new QLabel(description(), widget);
Laurent Montel's avatar
Laurent Montel committed
654
    label->setBuddy(mFileNameRequester);
655
    glay->addWidget(label, row, 1);
Laurent Montel's avatar
Laurent Montel committed
656
    glay->addWidget(mFileNameRequester, row, 2);
657 658
    if (entry->isReadOnly()) {
        label->setEnabled(false);
Laurent Montel's avatar
Laurent Montel committed
659
        mFileNameRequester->setEnabled(false);
660
    } else {
Laurent Montel's avatar
Laurent Montel committed
661
        connect(mFileNameRequester, &FileNameRequester::fileNameChanged, this, &CryptoConfigEntryDirPath::slotChanged);
662 663 664 665 666 667 668 669 670 671 672 673 674 675 676 677 678
    }
}

void Kleo::CryptoConfigEntryDirPath::doSave()
{
    mEntry->setURLValue(QUrl::fromLocalFile(mFileNameRequester->fileName()));
}

void Kleo::CryptoConfigEntryDirPath::doLoad()
{
    mFileNameRequester->setFileName(mEntry->urlValue().toLocalFile());
}

////

Kleo::CryptoConfigEntrySpinBox::CryptoConfigEntrySpinBox(
    CryptoConfigModule *module,
679
    QGpgME::CryptoConfigEntry *entry, const QString &entryName,
680 681 682 683
    QGridLayout *glay, QWidget *widget)
    : CryptoConfigEntryGUI(module, entry, entryName)
{

684
    if (entry->argType() == QGpgME::CryptoConfigEntry::ArgType_None && entry->isList()) {
685
        mKind = ListOfNone;
686
    } else if (entry->argType() == QGpgME::CryptoConfigEntry::ArgType_UInt) {
687 688
        mKind = UInt;
    } else {
689
        Q_ASSERT(entry->argType() == QGpgME::CryptoConfigEntry::ArgType_Int);
690 691 692 693 694 695 696 697 698 699 700 701 702 703
        mKind = Int;
    }

    const int row = glay->rowCount();
    mNumInput = new QSpinBox(widget);
    QLabel *label = new QLabel(description(), widget);
    label->setBuddy(mNumInput);
    glay->addWidget(label, row, 1);
    glay->addWidget(mNumInput, row, 2);

    if (entry->isReadOnly()) {
        label->setEnabled(false);
        mNumInput->setEnabled(false);
    } else {
704 705
        mNumInput->setMinimum(mKind == Int ? std::numeric_limits<int>::min() : 0);
        mNumInput->setMaximum(std::numeric_limits<int>::max());
Laurent Montel's avatar
Laurent Montel committed
706
        connect(mNumInput, QOverload<int>::of(&QSpinBox::valueChanged), this, &CryptoConfigEntrySpinBox::slotChanged);
707 708 709 710 711 712 713 714 715 716 717 718 719 720 721 722 723 724 725 726 727 728 729 730 731 732 733 734 735 736 737 738 739 740 741 742 743 744 745 746
    }
}

void Kleo::CryptoConfigEntrySpinBox::doSave()
{
    int value = mNumInput->value();
    switch (mKind) {
    case ListOfNone:
        mEntry->setNumberOfTimesSet(value);
        break;
    case UInt:
        mEntry->setUIntValue(value);
        break;
    case Int:
        mEntry->setIntValue(value);
        break;
    }
}

void Kleo::CryptoConfigEntrySpinBox::doLoad()
{
    int value = 0;
    switch (mKind) {
    case ListOfNone:
        value = mEntry->numberOfTimesSet();
        break;
    case UInt:
        value = mEntry->uintValue();
        break;
    case Int:
        value = mEntry->intValue();
        break;
    }
    mNumInput->setValue(value);
}

////

Kleo::CryptoConfigEntryCheckBox::CryptoConfigEntryCheckBox(
    CryptoConfigModule *module,
747
    QGpgME::CryptoConfigEntry *entry, const QString &entryName,
748 749 750 751 752 753 754 755 756 757 758 759 760 761 762 763 764 765 766 767 768 769 770 771 772 773
    QGridLayout *glay, QWidget *widget)
    : CryptoConfigEntryGUI(module, entry, entryName)
{
    const int row = glay->rowCount();
    mCheckBox = new QCheckBox(widget);
    glay->addWidget(mCheckBox, row, 1, 1, 2);
    mCheckBox->setText(description());
    if (entry->isReadOnly()) {
        mCheckBox->setEnabled(false);
    } else {
        connect(mCheckBox, &QCheckBox::toggled, this, &CryptoConfigEntryCheckBox::slotChanged);
    }
}

void Kleo::CryptoConfigEntryCheckBox::doSave()
{
    mEntry->setBoolValue(mCheckBox->isChecked());
}

void Kleo::CryptoConfigEntryCheckBox::doLoad()
{
    mCheckBox->setChecked(mEntry->boolValue());
}

Kleo::CryptoConfigEntryLDAPURL::CryptoConfigEntryLDAPURL(
    CryptoConfigModule *module,
774
    QGpgME::CryptoConfigEntry *entry,
775 776 777 778 779 780 781 782 783 784 785 786 787 788 789 790 791 792 793 794 795 796 797 798 799 800 801 802 803 804 805 806 807 808
    const QString &entryName,
    QGridLayout *glay, QWidget *widget)
    : CryptoConfigEntryGUI(module, entry, entryName)
{
    mLabel = new QLabel(widget);
    mPushButton = new QPushButton(entry->isReadOnly() ? i18n("Show...") : i18n("Edit..."), widget);

    const int row = glay->rowCount();
    QLabel *label = new QLabel(description(), widget);
    label->setBuddy(mPushButton);
    glay->addWidget(label, row, 1);
    QHBoxLayout *hlay = new QHBoxLayout;
    glay->addLayout(hlay, row, 2);
    hlay->addWidget(mLabel, 1);
    hlay->addWidget(mPushButton);

    if (entry->isReadOnly()) {
        mLabel->setEnabled(false);
    }
    connect(mPushButton, &QPushButton::clicked, this, &CryptoConfigEntryLDAPURL::slotOpenDialog);
}

void Kleo::CryptoConfigEntryLDAPURL::doLoad()
{
    setURLList(mEntry->urlValueList());
}

void Kleo::CryptoConfigEntryLDAPURL::doSave()
{
    mEntry->setURLValueList(mURLList);
}

void prepareURLCfgDialog(QDialog *dialog, DirectoryServicesWidget *dirserv, bool readOnly)
{
809
    QDialogButtonBox *buttonBox = new QDialogButtonBox(QDialogButtonBox::Ok, dialog);
810 811 812 813 814 815 816

    if (!readOnly) {
        buttonBox->addButton(QDialogButtonBox::Cancel);
        buttonBox->addButton(QDialogButtonBox::RestoreDefaults);

        QPushButton *defaultsBtn = buttonBox->button(QDialogButtonBox::RestoreDefaults);

817
        QObject::connect(defaultsBtn, &QPushButton::clicked, dirserv, &DirectoryServicesWidget::clear);
818 819 820 821 822 823 824 825 826 827 828 829 830 831 832 833
        QObject::connect(buttonBox, &QDialogButtonBox::rejected, dialog, &QDialog::reject);
    }

    QObject::connect(buttonBox, &QDialogButtonBox::accepted, dialog, &QDialog::accept);

    QVBoxLayout *layout = new QVBoxLayout;
    layout->addWidget(dirserv);
    layout->addWidget(buttonBox);
    dialog->setLayout(layout);
}

void Kleo::CryptoConfigEntryLDAPURL::slotOpenDialog()
{
    // I'm a bad boy and I do it all on the stack. Enough classes already :)
    // This is just a simple dialog around the directory-services-widget
    QDialog dialog(mPushButton->parentWidget());
834
    dialog.setWindowTitle(i18nc("@title:window", "Configure LDAP Servers"));
835 836 837 838 839 840 841 842 843 844 845 846 847 848 849 850 851 852 853 854

    DirectoryServicesWidget *dirserv = new DirectoryServicesWidget(&dialog);

    prepareURLCfgDialog(&dialog, dirserv, mEntry->isReadOnly());

    dirserv->setX509ReadOnly(mEntry->isReadOnly());
    dirserv->setAllowedSchemes(DirectoryServicesWidget::LDAP);
    dirserv->setAllowedProtocols(DirectoryServicesWidget::X509Protocol);
    dirserv->addX509Services(mURLList);

    if (dialog.exec()) {
        setURLList(dirserv->x509Services());
        slotChanged();
    }
}

void Kleo::CryptoConfigEntryLDAPURL::setURLList(const QList<QUrl> &urlList)
{
    mURLList = urlList;
    if (mURLList.isEmpty()) {
Andre Heinecke's avatar
Andre Heinecke committed
855
        mLabel->setText(i18n("None configured"));
856 857 858 859 860 861 862
    } else {
        mLabel->setText(i18np("1 server configured", "%1 servers configured", mURLList.count()));
    }
}

Kleo::CryptoConfigEntryKeyserver::CryptoConfigEntryKeyserver(
    CryptoConfigModule *module,
863
    QGpgME::CryptoConfigEntry *entry,
864 865 866 867 868 869 870 871 872 873 874 875 876 877 878 879 880 881 882 883 884 885 886 887 888 889 890 891 892 893 894 895 896 897 898 899 900 901 902 903 904 905 906 907 908 909 910 911 912 913 914 915 916 917 918 919
    const QString &entryName,
    QGridLayout *glay, QWidget *widget)
    : CryptoConfigEntryGUI(module, entry, entryName)
{
    mLabel = new QLabel(widget);
    mPushButton = new QPushButton(i18n("Edit..."), widget);

    const int row = glay->rowCount();
    QLabel *label = new QLabel(i18n("Use keyserver at"), widget);
    label->setBuddy(mPushButton);
    glay->addWidget(label, row, 1);
    QHBoxLayout *hlay = new QHBoxLayout;
    glay->addLayout(hlay, row, 2);
    hlay->addWidget(mLabel, 1);
    hlay->addWidget(mPushButton);

    if (entry->isReadOnly()) {
        mLabel->setEnabled(false);
        mPushButton->hide();
    } else {
        connect(mPushButton, &QPushButton::clicked, this, &CryptoConfigEntryKeyserver::slotOpenDialog);
    }
}

Kleo::ParsedKeyserver Kleo::parseKeyserver(const QString &str)
{
    const QStringList list = str.split(QRegExp(QLatin1String("[\\s,]")), QString::SkipEmptyParts);
    if (list.empty()) {
        return Kleo::ParsedKeyserver();
    }
    Kleo::ParsedKeyserver result;
    result.url = list.front();
    Q_FOREACH (const QString &kvpair, list.mid(1)) {
        const int idx = kvpair.indexOf(QLatin1Char('='));
        if (idx < 0) {
            result.options.push_back(qMakePair(kvpair, QString()));     // null QString
        } else {
            const QString key = kvpair.left(idx);
            const QString value = kvpair.mid(idx + 1);
            if (value.isEmpty()) {
                result.options.push_back(qMakePair(key, QStringLiteral("")));    // make sure it's not a null QString, only an empty one
            } else {
                result.options.push_back(qMakePair(key, value));
            }
        }
    }
    return result;
}

QString Kleo::assembleKeyserver(const ParsedKeyserver &keyserver)
{
    if (keyserver.options.empty()) {
        return keyserver.url;
    }
    QString result = keyserver.url;
    typedef QPair<QString, QString> Pair;
Laurent Montel's avatar
Laurent Montel committed
920
    for (const Pair &pair : qAsConst(keyserver.options))
921 922 923 924 925 926 927 928 929 930 931 932 933 934 935 936 937 938 939 940 941 942 943 944 945 946 947 948 949 950 951 952 953 954 955 956 957 958 959 960
        if (pair.second.isNull()) {
            result += QLatin1Char(' ') + pair.first;
        } else {
            result += QLatin1Char(' ') + pair.first + QLatin1Char('=') + pair.second;
        }
    return result;
}

void Kleo::CryptoConfigEntryKeyserver::doLoad()
{
    mParsedKeyserver = parseKeyserver(mEntry->stringValue());
    mLabel->setText(mParsedKeyserver.url);
}

void Kleo::CryptoConfigEntryKeyserver::doSave()
{
    mParsedKeyserver.url = mLabel->text();
    mEntry->setStringValue(assembleKeyserver(mParsedKeyserver));
}

static QList<QUrl> string2urls(const QString &str)
{
    QList<QUrl> ret;
    if (str.isEmpty()) {
        return ret;
    }
    ret << QUrl::fromUserInput(str);
    return ret;
}

static QString urls2string(const QList<QUrl> &urls)
{
    return urls.empty() ? QString() : urls.front().url();
}

void Kleo::CryptoConfigEntryKeyserver::slotOpenDialog()
{
    // I'm a bad boy and I do it all on the stack. Enough classes already :)
    // This is just a simple dialog around the directory-services-widget
    QDialog dialog(mPushButton->parentWidget());
961
    dialog.setWindowTitle(i18nc("@title:window", "Configure Keyservers"));
962 963 964 965 966 967 968 969 970 971 972 973 974 975 976 977 978

    DirectoryServicesWidget *dirserv = new DirectoryServicesWidget(&dialog);

    prepareURLCfgDialog(&dialog, dirserv, mEntry->isReadOnly());

    dirserv->setOpenPGPReadOnly(mEntry->isReadOnly());
    dirserv->setAllowedSchemes(DirectoryServicesWidget::AllSchemes);
    dirserv->setAllowedProtocols(DirectoryServicesWidget::OpenPGPProtocol);
    dirserv->addOpenPGPServices(string2urls(mLabel->text()));

    if (dialog.exec()) {
        mLabel->setText(urls2string(dirserv->openPGPServices()));
        slotChanged();
    }
}

#include "moc_cryptoconfigmodule_p.cpp"