Commit 29a4359f authored by Ingo Klöcker's avatar Ingo Klöcker
Browse files

Simplify the Add User ID dialog

Reimplement the dialog from scratch
* removing the *.ui file,
* leaving out the comment field,
* leaving out the required labels because marking name and email as
optional is confusing given that one of them must be entered,
* leaving out the error handling for now; accessible error handling will
be added in the next step

GnuPG-bug-id: 5916
parent 26fe7b5b
......@@ -32,7 +32,7 @@ option(DISABLE_KWATCHGNUPG "Don't build the kwatchgnupg tool [default=OFF]" OFF)
# Standalone build. Find / include everything necessary.
set(KF5_MIN_VERSION "5.92.0")
set(KMIME_VERSION "5.20.40")
set(LIBKLEO_VERSION "5.20.40")
set(LIBKLEO_VERSION "5.20.41")
set(QT_REQUIRED_VERSION "5.15.2")
set(GPGME_REQUIRED_VERSION "1.15.0")
......
......@@ -316,7 +316,6 @@ ki18n_wrap_ui(_kleopatra_SRCS
dialogs/ownertrustdialog.ui
dialogs/selectchecklevelwidget.ui
dialogs/selftestdialog.ui
dialogs/adduseriddialog.ui
dialogs/setinitialpindialog.ui
dialogs/trustchainwidget.ui
dialogs/subkeyswidget.ui
......
......@@ -27,7 +27,6 @@
using namespace Kleo;
using namespace Kleo::Commands;
using namespace Kleo::Dialogs;
using namespace GpgME;
class AddUserIDCommand::Private : public Command::Private
......@@ -137,7 +136,6 @@ void AddUserIDCommand::doStart()
d->dialog->setName(QString::fromUtf8(uid.name()));
d->dialog->setEmail(Formatting::prettyEMail(uid.email(), uid.id()));
d->dialog->setComment(QString::fromUtf8(uid.comment()));
d->dialog->show();
}
......@@ -150,7 +148,7 @@ void AddUserIDCommand::Private::slotDialogAccepted()
if (!job) {
finished();
}
else if (const Error err = job->start(key, dialog->name(), dialog->email(), dialog->comment())) {
else if (const Error err = job->start(key, dialog->name(), dialog->email())) {
showErrorDialog(err);
finished();
}
......
......@@ -38,7 +38,6 @@
using namespace Kleo;
using namespace Kleo::Commands;
using namespace Kleo::Dialogs;
using namespace Kleo::SmartCard;
using namespace GpgME;
using namespace QGpgME;
......@@ -152,7 +151,7 @@ void CreateOpenPGPKeyFromCardKeysCommand::Private::slotDialogAccepted()
connect(job, SIGNAL(result(GpgME::Error)),
q, SLOT(slotResult(GpgME::Error)));
const QString userID = Formatting::prettyNameAndEMail(OpenPGP, QString(), dialog->name(), dialog->email(), dialog->comment());
const QString userID = Formatting::prettyNameAndEMail(OpenPGP, QString(), dialog->name(), dialog->email());
const QDateTime expires = QDateTime();
const unsigned int flags = GPGME_CREATE_FORCE;
job->startCreate(userID, "card", expires, Key(), flags);
......
......@@ -3,6 +3,8 @@
This file is part of Kleopatra, the KDE keymanager
SPDX-FileCopyrightText: 2008 Klarälvdalens Datakonsult AB
SPDX-FileCopyrightText: 2022 g10 Code GmbH
SPDX-FileContributor: Ingo Klöcker <dev@ingo-kloecker.de>
SPDX-License-Identifier: GPL-2.0-or-later
*/
......@@ -11,329 +13,168 @@
#include "adduseriddialog.h"
#include "ui_adduseriddialog.h"
#include <utils/validation.h>
#include <Libkleo/Stl_Util>
#include <KLocalizedString>
#include <KSeparator>
#include <QString>
#include <QStringList>
#include <QPushButton>
#include <QValidator>
#include <QDialogButtonBox>
#include <QGridLayout>
#include <QLabel>
#include <QLineEdit>
#include <QVBoxLayout>
#include <KConfigGroup>
#include <KLocalizedString>
#include "kleopatra_debug.h"
#include <KSharedConfig>
using namespace Kleo;
using namespace Kleo::Dialogs;
namespace
{
struct Line {
QString attr;
QString label;
QString regex;
QLineEdit *edit = nullptr;
};
}
static QString pgpLabel(const QString &attr)
QString buildUserId(const QString &name, const QString &email)
{
if (attr == QLatin1String("NAME")) {
return i18n("Name");
} else if (attr == QLatin1String("COMMENT")) {
return i18n("Comment");
} else if (attr == QLatin1String("EMAIL")) {
return i18n("EMail");
}
return QString();
if (name.isEmpty()) {
return email;
} else if (email.isEmpty()) {
return name;
} else {
return QStringLiteral("%1 <%2>").arg(name, email);
}
}
static QString attributeLabel(const QString &attr, bool pgp)
{
if (attr.isEmpty()) {
return QString();
}
const QString label = /*pgp ?*/ pgpLabel(attr) /*: Kleo::DNAttributeMapper::instance()->name2label( attr )*/;
if (!label.isEmpty())
if (pgp) {
return label;
} else
return i18nc("Format string for the labels in the \"Your Personal Data\" page",
"%1 (%2)", label, attr);
else {
return attr;
}
}
static QString attributeFromKey(QString key)
{
return key.remove(QLatin1Char('!'));
}
static int row_index_of(QWidget *w, QGridLayout *l)
{
const int idx = l->indexOf(w);
int r, c, rs, cs;
l->getItemPosition(idx, &r, &c, &rs, &cs);
return r;
}
static QLineEdit *adjust_row(QGridLayout *l, int row, const QString &label, const QString &preset, QValidator *validator, bool readonly, bool required)
{
Q_ASSERT(l);
Q_ASSERT(row >= 0);
Q_ASSERT(row < l->rowCount());
auto lb = qobject_cast<QLabel *>(l->itemAtPosition(row, 0)->widget());
Q_ASSERT(lb);
auto le = qobject_cast<QLineEdit *>(l->itemAtPosition(row, 1)->widget());
Q_ASSERT(le);
auto reqLB = qobject_cast<QLabel *>(l->itemAtPosition(row, 2)->widget());
Q_ASSERT(reqLB);
lb->setText(i18nc("interpunctation for labels", "%1:", label));
le->setText(preset);
reqLB->setText(required ? i18n("(required)") : i18n("(optional)"));
delete le->validator();
if (validator) {
if (!validator->parent()) {
validator->setParent(le);
}
le->setValidator(validator);
}
le->setReadOnly(readonly && le->hasAcceptableInput());
lb->show();
le->show();
reqLB->show();
return le;
}
class AddUserIDDialog::Private
{
friend class ::Kleo::Dialogs::AddUserIDDialog;
friend class ::Kleo::AddUserIDDialog;
AddUserIDDialog *const q;
struct {
QLineEdit *nameEdit;
QLineEdit *emailEdit;
QLabel *resultLabel;
QDialogButtonBox *buttonBox;
} ui;
public:
explicit Private(AddUserIDDialog *qq)
: q(qq),
ui(q)
: q{qq}
{
}
q->setWindowTitle(i18nc("title:window", "Add User ID"));
private:
void slotUserIDChanged();
auto mainLayout = new QVBoxLayout{q};
private:
bool isComplete() const;
mainLayout->addWidget(new QLabel{i18n("Enter the name and/or the email address to use for the user ID."), q});
private:
struct UI : public Ui_AddUserIDDialog {
auto gridLayout = new QGridLayout;
int row = -1;
QVector<Line> lineList;
{
auto label = new QLabel{i18nc("@label", "Name:"), q};
ui.nameEdit = new QLineEdit{q};
label->setBuddy(ui.nameEdit);
row++;
gridLayout->addWidget(label, row, 0, 1, 1);
gridLayout->addWidget(ui.nameEdit, row, 1, 1, 1);
}
connect(ui.nameEdit, &QLineEdit::textChanged,
q, [this]() { onNameChanged(); });
explicit UI(AddUserIDDialog *qq)
: Ui_AddUserIDDialog()
{
setupUi(qq);
// ### this code is mostly the same as the one in
// ### newcertificatewizard. Find some time to factor them
// ### into a single copy.
// hide the stuff
nameLB->hide();
nameLE->hide();
nameRequiredLB->hide();
emailLB->hide();
emailLE->hide();
emailRequiredLB->hide();
commentLB->hide();
commentLE->hide();
commentRequiredLB->hide();
// set errorLB to have a fixed height of two lines:
errorLB->setText(QStringLiteral("2<br>1"));
errorLB->setFixedHeight(errorLB->minimumSizeHint().height());
errorLB->clear();
const KConfigGroup config(KSharedConfig::openConfig(), "CertificateCreationWizard");
const QStringList attrOrder = config.readEntry("OpenPGPAttributeOrder",
QStringList() << QStringLiteral("NAME") << QStringLiteral("EMAIL") << QStringLiteral("COMMENT"));
QMap<int, Line> lines;
for (const QString &rawKey : attrOrder) {
const QString key = rawKey.trimmed().toUpper();
const QString attr = attributeFromKey(key);
if (attr.isEmpty()) {
continue;
}
const QString preset = config.readEntry(attr);
const bool required = key.endsWith(QLatin1Char('!'));
const bool readonly = config.isEntryImmutable(attr);
const QString label = config.readEntry(attr + QLatin1String("_label"),
attributeLabel(attr, true));
const QString regex = config.readEntry(attr + QLatin1String("_regex"));
int row;
QValidator *validator = nullptr;
if (attr == QLatin1String("EMAIL")) {
validator = regex.isEmpty() ? Validation::email() : Validation::email(QRegExp(regex));
row = row_index_of(emailLE, gridLayout);
} else if (attr == QLatin1String("NAME")) {
validator = regex.isEmpty() ? Validation::pgpName() : Validation::pgpName(QRegExp(regex));
row = row_index_of(nameLE, gridLayout);
} else if (attr == QLatin1String("COMMENT")) {
validator = regex.isEmpty() ? Validation::pgpComment() : Validation::pgpComment(QRegExp(regex));
row = row_index_of(commentLE, gridLayout);
} else {
continue;
}
QLineEdit *le = adjust_row(gridLayout, row, label, preset, validator, readonly, required);
const Line line = { key, label, regex, le };
lines[row] = line;
}
std::copy(lines.begin(), lines.end(), std::back_inserter(lineList));
QObject::connect(nameLE, SIGNAL(textChanged(QString)), qq, SLOT(slotUserIDChanged()));
QObject::connect(emailLE, SIGNAL(textChanged(QString)), qq, SLOT(slotUserIDChanged()));
QObject::connect(commentLE, SIGNAL(textChanged(QString)), qq, SLOT(slotUserIDChanged()));
auto label = new QLabel{i18nc("@label", "Email:"), q};
ui.emailEdit = new QLineEdit{q};
label->setBuddy(ui.emailEdit);
row++;
gridLayout->addWidget(label, row, 0, 1, 1);
gridLayout->addWidget(ui.emailEdit, row, 1, 1, 1);
}
connect(ui.emailEdit, &QLineEdit::textChanged,
q, [this]() { onEmailChanged(); });
mainLayout->addLayout(gridLayout);
mainLayout->addWidget(new KSeparator{Qt::Horizontal, q});
QPushButton *okPB() const
{
return buttonBox->button(QDialogButtonBox::Ok);
auto label = new QLabel{i18n("This is how the new user ID will be stored in the certificate:"), q};
mainLayout->addWidget(label);
}
{
ui.resultLabel = new QLabel{q};
ui.resultLabel->setMinimumSize(300, 0);
ui.resultLabel->setAlignment(Qt::AlignCenter);
ui.resultLabel->setTextFormat(Qt::PlainText);
QFont font;
font.setBold(true);
font.setWeight(75);
ui.resultLabel->setFont(font);
mainLayout->addWidget(ui.resultLabel);
}
} ui;
};
AddUserIDDialog::AddUserIDDialog(QWidget *p)
: QDialog(p), d(new Private(this))
{
// explicitly trigger an update after setup is complete
d->slotUserIDChanged();
}
mainLayout->addWidget(new KSeparator{Qt::Horizontal, q});
AddUserIDDialog::~AddUserIDDialog() {}
mainLayout->addStretch(1);
void AddUserIDDialog::setName(const QString &name)
{
d->ui.nameLE->setText(name);
}
ui.buttonBox = new QDialogButtonBox{QDialogButtonBox::Ok | QDialogButtonBox::Cancel, q};
QString AddUserIDDialog::name() const
{
return d->ui.nameLE->text().trimmed();
}
mainLayout->addWidget(ui.buttonBox);
void AddUserIDDialog::setEmail(const QString &email)
{
d->ui.emailLE->setText(email);
}
connect(ui.buttonBox, &QDialogButtonBox::accepted, q, &QDialog::accept);
connect(ui.buttonBox, &QDialogButtonBox::rejected, q, &QDialog::reject);
QString AddUserIDDialog::email() const
{
return d->ui.emailLE->text().trimmed();
}
updateResultLabel();
}
void AddUserIDDialog::setComment(const QString &comment)
{
d->ui.commentLE->setText(comment);
}
QString name() const
{
return ui.nameEdit->text().trimmed();
}
QString email() const
{
return ui.emailEdit->text().trimmed();
}
private:
void onNameChanged()
{
updateResultLabel();
}
void onEmailChanged()
{
updateResultLabel();
}
void updateResultLabel()
{
ui.resultLabel->setText(buildUserId(name(), email()));
}
};
QString AddUserIDDialog::comment() const
AddUserIDDialog::AddUserIDDialog(QWidget *parent, Qt::WindowFlags f)
: QDialog{parent, f}
, d(new Private{this})
{
return d->ui.commentLE->text().trimmed();
}
static bool has_intermediate_input(const QLineEdit *le)
AddUserIDDialog::~AddUserIDDialog() = default;
void AddUserIDDialog::setName(const QString &name)
{
QString text = le->text();
int pos = le->cursorPosition();
const QValidator *const v = le->validator();
return !v || v->validate(text, pos) == QValidator::Intermediate;
d->ui.nameEdit->setText(name);
}
static bool requirementsAreMet(const QVector<Line> &list, QString &error)
QString AddUserIDDialog::name() const
{
bool allEmpty = true;
for (const Line &line : list) {
const QLineEdit *le = line.edit;
if (!le) {
continue;
}
const QString key = line.attr;
qCDebug(KLEOPATRA_LOG) << "requirementsAreMet(): checking \"" << key << "\" against \"" << le->text() << "\":";
if (le->text().trimmed().isEmpty()) {
if (key.endsWith(QLatin1Char('!'))) {
if (line.regex.isEmpty()) {
error = xi18nc("@info", "<interface>%1</interface> is required, but empty.", line.label);
} else
error = xi18nc("@info", "<interface>%1</interface> is required, but empty.<nl/>"
"Local Admin rule: <icode>%2</icode>", line.label, line.regex);
return false;
}
} else if (has_intermediate_input(le)) {
if (line.regex.isEmpty()) {
error = xi18nc("@info", "<interface>%1</interface> is incomplete.", line.label);
} else
error = xi18nc("@info", "<interface>%1</interface> is incomplete.<nl/>"
"Local Admin rule: <icode>%2</icode>", line.label, line.regex);
return false;
} else if (!le->hasAcceptableInput()) {
if (line.regex.isEmpty()) {
error = xi18nc("@info", "<interface>%1</interface> is invalid.", line.label);
} else
error = xi18nc("@info", "<interface>%1</interface> is invalid.<nl/>"
"Local Admin rule: <icode>%2</icode>", line.label, line.regex);
return false;
} else {
allEmpty = false;
}
}
return !allEmpty;
return d->name();
}
bool AddUserIDDialog::Private::isComplete() const
void AddUserIDDialog::setEmail(const QString &email)
{
QString error;
const bool ok = requirementsAreMet(ui.lineList, error);
ui.errorLB->setText(error);
return ok;
d->ui.emailEdit->setText(email);
}
void AddUserIDDialog::Private::slotUserIDChanged()
QString AddUserIDDialog::email() const
{
ui.okPB()->setEnabled(isComplete());
const QString name = q->name();
const QString email = q->email();
const QString comment = q->comment();
QStringList parts;
if (!name.isEmpty()) {
parts.push_back(name);
}
if (!comment.isEmpty()) {
parts.push_back(QLatin1Char('(') + comment + QLatin1Char(')'));
}
if (!email.isEmpty()) {
parts.push_back(QLatin1Char('<') + email + QLatin1Char('>'));
}
ui.resultLB->setText(parts.join(QLatin1Char(' ')));
return d->email();
}
#include "moc_adduseriddialog.cpp"
/* -*- mode: c++; c-basic-offset:4 -*-
dialogs/addUserIDdialog.h
dialogs/adduseriddialog.h
This file is part of Kleopatra, the KDE keymanager
SPDX-FileCopyrightText: 2008 Klarälvdalens Datakonsult AB
SPDX-FileCopyrightText: 2022 g10 Code GmbH
SPDX-FileContributor: Ingo Klöcker <dev@ingo-kloecker.de>
SPDX-License-Identifier: GPL-2.0-or-later
*/
......@@ -11,23 +13,16 @@
#include <QDialog>
#include <utils/pimpl_ptr.h>
class QString;
#include <memory>
namespace Kleo
{
namespace Dialogs
{
class AddUserIDDialog : public QDialog
{
Q_OBJECT
Q_PROPERTY(QString name READ name WRITE setName)
Q_PROPERTY(QString email READ email WRITE setEmail)
Q_PROPERTY(QString comment READ comment WRITE setComment)
public:
explicit AddUserIDDialog(QWidget *parent = nullptr);
explicit AddUserIDDialog(QWidget *parent = nullptr, Qt::WindowFlags f = {});
~AddUserIDDialog() override;
void setName(const QString &name);
......@@ -36,15 +31,9 @@ public:
void setEmail(const QString &email);
QString email() const;
void setComment(const QString &comment);
QString comment() const;
private:
class Private;
kdtools::pimpl_ptr<Private> d;
Q_PRIVATE_SLOT(d, void slotUserIDChanged())
const std::unique_ptr<Private> d;
};
}
}
} // namespace Kleo
<ui version="4.0" >
<class>AddUserIDDialog</class>
<widget class="QDialog" name="AddUserIDDialog" >
<property name="windowTitle" >
<string>Add New User-ID</string>
</property>
<layout class="QGridLayout" >
<item row="0" column="0" >
<widget class="QLabel" name="nameLB" >
<property name="buddy" >
<cstring>nameLE</cstring>
</property>
</widget>
</item>
<item row="0" column="1" >
<widget class="QLineEdit" name="nameLE" />
</item>
<item row="1" column="0" >
<widget class="QLabel" name="emailLB" >
<property name="buddy" >
<cstring>emailLE</cstring>
</property>
</widget>
</item>
<item row="1" column="1" >
<widget class="QLineEdit" name="emailLE" />
</item>
<item row="2" column="0" >
<widget class="QLabel" name="commentLB" >
<property name="buddy" >
<cstring>commentLE</cstring>
</property>
</widget>
</item>
<item row="2" column="1" >