Page Menu
Home
GnuPG
Search
Configure Global Search
Log In
Files
F34134389
No One
Temporary
Actions
View File
Edit File
Delete File
View Transforms
Subscribe
Mute Notifications
Award Token
Size
32 KB
Subscribers
None
View Options
diff --git a/src/crypto/gui/certificatelineedit.cpp b/src/crypto/gui/certificatelineedit.cpp
index bc60a9f9f..8deb59dbd 100644
--- a/src/crypto/gui/certificatelineedit.cpp
+++ b/src/crypto/gui/certificatelineedit.cpp
@@ -1,906 +1,907 @@
/* crypto/gui/certificatelineedit.cpp
This file is part of Kleopatra, the KDE keymanager
SPDX-FileCopyrightText: 2016 Bundesamt für Sicherheit in der Informationstechnik
SPDX-FileContributor: Intevation GmbH
SPDX-FileCopyrightText: 2021, 2022 g10 Code GmbH
SPDX-FileContributor: Ingo Klöcker <dev@ingo-kloecker.de>
SPDX-License-Identifier: GPL-2.0-or-later
*/
#include "certificatelineedit.h"
#include "commands/detailscommand.h"
#include "dialogs/groupdetailsdialog.h"
#include "utils/accessibility.h"
#include <QAccessible>
#include <QAction>
#include <QCompleter>
#include <QPushButton>
#include <QSignalBlocker>
#include "kleopatra_debug.h"
#include <Libkleo/Debug>
#include <Libkleo/ErrorLabel>
#include <Libkleo/Formatting>
#include <Libkleo/KeyCache>
#include <Libkleo/KeyFilter>
#include <Libkleo/KeyGroup>
#include <Libkleo/KeyList>
#include <Libkleo/KeyListModel>
#include <Libkleo/KeyListSortFilterProxyModel>
#include <Libkleo/UserIDProxyModel>
#include <KLocalizedString>
#include <gpgme++/key.h>
#include <gpgme++/keylistresult.h>
#include <QGpgME/KeyListJob>
#include <QGpgME/Protocol>
#include <QHBoxLayout>
#include <QLineEdit>
#include <QMenu>
#include <QToolButton>
using namespace Kleo;
using namespace GpgME;
Q_DECLARE_METATYPE(GpgME::Key)
Q_DECLARE_METATYPE(KeyGroup)
static QStringList s_lookedUpKeys;
namespace
{
class CompletionProxyModel : public KeyListSortFilterProxyModel
{
Q_OBJECT
public:
CompletionProxyModel(QObject *parent = nullptr)
: KeyListSortFilterProxyModel(parent)
{
}
int columnCount(const QModelIndex &parent = QModelIndex()) const override
{
Q_UNUSED(parent)
// pretend that there is only one column to workaround a bug in
// QAccessibleTable which provides the accessibility interface for the
// completion pop-up
return 1;
}
QVariant data(const QModelIndex &idx, int role) const override
{
if (!idx.isValid()) {
return QVariant();
}
switch (role) {
case Qt::DecorationRole: {
const auto key = KeyListSortFilterProxyModel::data(idx, KeyList::KeyRole).value<GpgME::Key>();
if (!key.isNull()) {
return Kleo::Formatting::iconForUid(key.userID(0));
}
const auto userID = KeyListSortFilterProxyModel::data(idx, KeyList::UserIDRole).value<GpgME::UserID>();
if (!userID.isNull()) {
return Kleo::Formatting::iconForUid(userID);
}
const auto group = KeyListSortFilterProxyModel::data(idx, KeyList::GroupRole).value<KeyGroup>();
if (!group.isNull()) {
return QIcon::fromTheme(QStringLiteral("group"));
}
Q_ASSERT(!key.isNull() || !userID.isNull() || !group.isNull());
return QVariant();
}
default:
return KeyListSortFilterProxyModel::data(index(idx.row(), KeyList::Summary), role);
}
}
private:
bool lessThan(const QModelIndex &left, const QModelIndex &right) const override
{
const auto leftKey = sourceModel()->data(left, KeyList::KeyRole).value<GpgME::Key>();
const auto leftGroup = leftKey.isNull() ? sourceModel()->data(left, KeyList::GroupRole).value<KeyGroup>() : KeyGroup{};
const auto leftUserID = sourceModel()->data(left, KeyList::UserIDRole).value<GpgME::UserID>();
const auto rightUserID = sourceModel()->data(right, KeyList::UserIDRole).value<GpgME::UserID>();
const auto rightKey = sourceModel()->data(right, KeyList::KeyRole).value<GpgME::Key>();
const auto rightGroup = rightKey.isNull() ? sourceModel()->data(right, KeyList::GroupRole).value<KeyGroup>() : KeyGroup{};
// shouldn't happen, but still put null entries at the end
if (leftKey.isNull() && leftUserID.isNull() && leftGroup.isNull()) {
return false;
}
if (rightKey.isNull() && rightUserID.isNull() && rightGroup.isNull()) {
return true;
}
// first sort by the displayed name and/or email address
const auto leftNameAndOrEmail = leftGroup.isNull()
? (leftKey.isNull() ? Formatting::nameAndEmailForSummaryLine(leftUserID) : Formatting::nameAndEmailForSummaryLine(leftKey))
: leftGroup.name();
const auto rightNameAndOrEmail = rightGroup.isNull()
? (rightKey.isNull() ? Formatting::nameAndEmailForSummaryLine(rightUserID) : Formatting::nameAndEmailForSummaryLine(rightKey))
: rightGroup.name();
const int cmp = QString::localeAwareCompare(leftNameAndOrEmail, rightNameAndOrEmail);
if (cmp) {
return cmp < 0;
}
// then sort groups before certificates
if (!leftGroup.isNull() && (!rightKey.isNull() || !rightUserID.isNull())) {
return true; // left is group, right is certificate
}
if ((!leftKey.isNull() || !rightKey.isNull()) && !rightGroup.isNull()) {
return false; // left is certificate, right is group
}
// if both are groups (with identical names) sort them by their ID
if (!leftGroup.isNull() && !rightGroup.isNull()) {
return leftGroup.id() < rightGroup.id();
}
// sort certificates with same name/email by validity and creation time
const auto lUid = leftUserID.isNull() ? leftKey.userID(0) : leftUserID;
const auto rUid = rightUserID.isNull() ? rightKey.userID(0) : rightUserID;
if (lUid.validity() != rUid.validity()) {
return lUid.validity() > rUid.validity();
}
/* Both have the same validity, check which one is newer. */
time_t leftTime = 0;
for (const GpgME::Subkey &s : (leftUserID.isNull() ? leftKey : leftUserID.parent()).subkeys()) {
if (s.isBad()) {
continue;
}
if (s.creationTime() > leftTime) {
leftTime = s.creationTime();
}
}
time_t rightTime = 0;
for (const GpgME::Subkey &s : (rightUserID.isNull() ? rightKey : rightUserID.parent()).subkeys()) {
if (s.isBad()) {
continue;
}
if (s.creationTime() > rightTime) {
rightTime = s.creationTime();
}
}
if (rightTime != leftTime) {
return leftTime > rightTime;
}
// as final resort we compare the fingerprints
return strcmp((leftUserID.isNull() ? leftKey : leftUserID.parent()).primaryFingerprint(),
(rightUserID.isNull() ? rightKey : rightUserID.parent()).primaryFingerprint())
< 0;
}
};
auto createSeparatorAction(QObject *parent)
{
auto action = new QAction{parent};
action->setSeparator(true);
return action;
}
} // namespace
class CertificateLineEdit::Private
{
CertificateLineEdit *q;
public:
enum class Status {
Empty, //< text is empty
Success, //< a certificate or group is set
None, //< entered text does not match any certificates or groups
Ambiguous, //< entered text matches multiple certificates or groups
Revoked, //< the selected cert is revoked
Expired, //< the selected cert is expired
};
enum class CursorPositioning {
MoveToEnd,
KeepPosition,
MoveToStart,
Default = MoveToEnd,
};
explicit Private(CertificateLineEdit *qq, AbstractKeyListModel *model, KeyUsage::Flags usage, KeyFilter *filter);
QString text() const;
void setKey(const GpgME::Key &key);
void setGroup(const KeyGroup &group);
void setUserID(const GpgME::UserID &userID);
void setKeyFilter(const std::shared_ptr<KeyFilter> &filter);
void setAccessibleName(const QString &s);
private:
void updateKey(CursorPositioning positioning);
void editChanged();
void editFinished();
void checkLocate();
void onLocateJobResult(QGpgME::Job *job, const QString &email, const KeyListResult &result, const std::vector<GpgME::Key> &keys);
void openDetailsDialog();
void setTextWithBlockedSignals(const QString &s, CursorPositioning positioning);
void showContextMenu(const QPoint &pos);
QString errorMessage() const;
QIcon statusIcon() const;
QString statusToolTip() const;
void updateStatusAction();
void updateErrorLabel();
void updateAccessibleNameAndDescription();
public:
Status mStatus = Status::Empty;
bool mEditingInProgress = false;
GpgME::Key mKey;
KeyGroup mGroup;
GpgME::UserID mUserId;
struct Ui {
explicit Ui(QWidget *parent)
: lineEdit{parent}
, button{parent}
, errorLabel{parent}
{
}
QLineEdit lineEdit;
QToolButton button;
ErrorLabel errorLabel;
} ui;
private:
QString mAccessibleName;
UserIDProxyModel *const mUserIdProxyModel;
KeyListSortFilterProxyModel *const mFilterModel;
CompletionProxyModel *const mCompleterFilterModel;
QCompleter *mCompleter = nullptr;
std::shared_ptr<KeyFilter> mFilter;
QAction *const mStatusAction;
QAction *const mShowDetailsAction;
QPointer<QGpgME::Job> mLocateJob;
Formatting::IconProvider mIconProvider;
};
CertificateLineEdit::Private::Private(CertificateLineEdit *qq, AbstractKeyListModel *model, KeyUsage::Flags usage, KeyFilter *filter)
: q{qq}
, ui{qq}
, mUserIdProxyModel{new UserIDProxyModel{qq}}
, mFilterModel{new KeyListSortFilterProxyModel{qq}}
, mCompleterFilterModel{new CompletionProxyModel{qq}}
, mCompleter{new QCompleter{qq}}
, mFilter{std::shared_ptr<KeyFilter>{filter}}
, mStatusAction{new QAction{qq}}
, mShowDetailsAction{new QAction{qq}}
, mIconProvider{usage}
{
ui.lineEdit.setPlaceholderText(i18nc("@info:placeholder", "Please enter a name or email address..."));
ui.lineEdit.setClearButtonEnabled(true);
ui.lineEdit.setContextMenuPolicy(Qt::CustomContextMenu);
ui.lineEdit.addAction(mStatusAction, QLineEdit::LeadingPosition);
mUserIdProxyModel->setSourceModel(model);
mCompleterFilterModel->setKeyFilter(mFilter);
mCompleterFilterModel->setSourceModel(mUserIdProxyModel);
// initialize dynamic sorting
mCompleterFilterModel->sort(0);
mCompleter->setModel(mCompleterFilterModel);
mCompleter->setFilterMode(Qt::MatchContains);
mCompleter->setCaseSensitivity(Qt::CaseInsensitive);
ui.lineEdit.setCompleter(mCompleter);
ui.button.setIcon(QIcon::fromTheme(QStringLiteral("resource-group-new")));
ui.button.setToolTip(i18nc("@info:tooltip", "Show certificate list"));
ui.button.setAccessibleName(i18n("Show certificate list"));
ui.errorLabel.setVisible(false);
auto vbox = new QVBoxLayout{q};
vbox->setContentsMargins(0, 0, 0, 0);
auto l = new QHBoxLayout;
l->setContentsMargins(0, 0, 0, 0);
l->addWidget(&ui.lineEdit);
l->addWidget(&ui.button);
vbox->addLayout(l);
vbox->addWidget(&ui.errorLabel);
q->setFocusPolicy(ui.lineEdit.focusPolicy());
q->setFocusProxy(&ui.lineEdit);
mShowDetailsAction->setIcon(QIcon::fromTheme(QStringLiteral("help-about")));
mShowDetailsAction->setText(i18nc("@action:inmenu", "Show Details"));
mShowDetailsAction->setEnabled(false);
mFilterModel->setSourceModel(mUserIdProxyModel);
mFilterModel->setFilterKeyColumn(KeyList::Summary);
if (filter) {
mFilterModel->setKeyFilter(mFilter);
}
connect(KeyCache::instance().get(), &Kleo::KeyCache::keysMayHaveChanged, q, [this]() {
updateKey(CursorPositioning::KeepPosition);
});
connect(KeyCache::instance().get(), &Kleo::KeyCache::groupUpdated, q, [this](const KeyGroup &group) {
if (!mGroup.isNull() && mGroup.source() == group.source() && mGroup.id() == group.id()) {
setTextWithBlockedSignals(Formatting::summaryLine(group), CursorPositioning::KeepPosition);
// queue the update to ensure that the model has been updated
QMetaObject::invokeMethod(
q,
[this]() {
updateKey(CursorPositioning::KeepPosition);
},
Qt::QueuedConnection);
}
});
connect(KeyCache::instance().get(), &Kleo::KeyCache::groupRemoved, q, [this](const KeyGroup &group) {
if (!mGroup.isNull() && mGroup.source() == group.source() && mGroup.id() == group.id()) {
mGroup = KeyGroup();
QSignalBlocker blocky{&ui.lineEdit};
ui.lineEdit.clear();
// queue the update to ensure that the model has been updated
QMetaObject::invokeMethod(
q,
[this]() {
updateKey(CursorPositioning::KeepPosition);
},
Qt::QueuedConnection);
}
});
connect(&ui.lineEdit, &QLineEdit::editingFinished, q, [this]() {
// queue the call of editFinished() to ensure that QCompleter::activated is handled first
QMetaObject::invokeMethod(
q,
[this]() {
editFinished();
},
Qt::QueuedConnection);
});
connect(&ui.lineEdit, &QLineEdit::textChanged, q, [this]() {
editChanged();
});
connect(&ui.lineEdit, &QLineEdit::customContextMenuRequested, q, [this](const QPoint &pos) {
showContextMenu(pos);
});
connect(mStatusAction, &QAction::triggered, q, [this]() {
openDetailsDialog();
});
connect(mShowDetailsAction, &QAction::triggered, q, [this]() {
openDetailsDialog();
});
connect(&ui.button, &QToolButton::clicked, q, &CertificateLineEdit::certificateSelectionRequested);
connect(mCompleter, qOverload<const QModelIndex &>(&QCompleter::activated), q, [this](const QModelIndex &index) {
Key key = mCompleter->completionModel()->data(index, KeyList::KeyRole).value<Key>();
auto group = mCompleter->completionModel()->data(index, KeyList::GroupRole).value<KeyGroup>();
auto userID = mCompleter->completionModel()->data(index, KeyList::UserIDRole).value<UserID>();
if (!userID.isNull()) {
q->setUserID(userID);
} else if (!key.isNull()) {
q->setKey(key);
} else if (!group.isNull()) {
q->setGroup(group);
} else {
qCDebug(KLEOPATRA_LOG) << "Activated item is neither key, nor userid, or group";
}
// queue the call of editFinished() to ensure that QLineEdit finished its own work
QMetaObject::invokeMethod(
q,
[this]() {
editFinished();
},
Qt::QueuedConnection);
});
updateKey(CursorPositioning::Default);
}
void CertificateLineEdit::Private::openDetailsDialog()
{
if (!q->key().isNull() || !q->userID().isNull()) {
const Key key = !q->key().isNull() ? q->key() : q->userID().parent();
auto cmd = new Commands::DetailsCommand{key};
cmd->setParentWidget(q);
cmd->start();
} else if (!q->group().isNull()) {
auto dlg = new Dialogs::GroupDetailsDialog{q};
dlg->setAttribute(Qt::WA_DeleteOnClose);
dlg->setGroup(q->group());
dlg->show();
}
}
void CertificateLineEdit::Private::setTextWithBlockedSignals(const QString &s, CursorPositioning positioning)
{
QSignalBlocker blocky{&ui.lineEdit};
const auto cursorPos = ui.lineEdit.cursorPosition();
ui.lineEdit.setText(s);
switch (positioning) {
case CursorPositioning::KeepPosition:
ui.lineEdit.setCursorPosition(cursorPos);
break;
case CursorPositioning::MoveToStart:
ui.lineEdit.setCursorPosition(0);
break;
case CursorPositioning::MoveToEnd:
default:; // setText() already moved the cursor to the end of the line
};
}
void CertificateLineEdit::Private::showContextMenu(const QPoint &pos)
{
if (QMenu *menu = ui.lineEdit.createStandardContextMenu()) {
auto *const firstStandardAction = menu->actions().value(0);
menu->insertActions(firstStandardAction, {mShowDetailsAction, createSeparatorAction(menu)});
menu->setAttribute(Qt::WA_DeleteOnClose);
menu->popup(ui.lineEdit.mapToGlobal(pos));
}
}
CertificateLineEdit::CertificateLineEdit(AbstractKeyListModel *model, KeyUsage::Flags usage, KeyFilter *filter, QWidget *parent)
: QWidget{parent}
, d{new Private{this, model, usage, filter}}
{
/* Take ownership of the model to prevent double deletion when the
* filter models are deleted */
model->setParent(parent ? parent : this);
}
CertificateLineEdit::~CertificateLineEdit() = default;
void CertificateLineEdit::Private::editChanged()
{
const bool editingStarted = !mEditingInProgress;
mEditingInProgress = true;
updateKey(CursorPositioning::Default);
if (editingStarted) {
Q_EMIT q->editingStarted();
}
if (q->isEmpty()) {
Q_EMIT q->cleared();
}
}
void CertificateLineEdit::Private::editFinished()
{
// perform a first update with the "editing in progress" flag still set
updateKey(CursorPositioning::MoveToStart);
mEditingInProgress = false;
checkLocate();
// perform another update with the "editing in progress" flag cleared
// after a key locate may have been started; this makes sure that displaying
// an error is delayed until the key locate job has finished
updateKey(CursorPositioning::MoveToStart);
}
void CertificateLineEdit::Private::checkLocate()
{
if (mStatus != Status::None) {
// try to locate key only if text matches no local certificates, user ids, or groups
return;
}
// Only check once per mailbox
const auto mailText = ui.lineEdit.text().trimmed();
if (mailText.isEmpty() || s_lookedUpKeys.contains(mailText)) {
return;
}
s_lookedUpKeys << mailText;
if (mLocateJob) {
mLocateJob->slotCancel();
mLocateJob.clear();
}
auto job = QGpgME::openpgp()->locateKeysJob();
connect(job, &QGpgME::KeyListJob::result, q, [this, job, mailText](const KeyListResult &result, const std::vector<GpgME::Key> &keys) {
onLocateJobResult(job, mailText, result, keys);
});
if (auto err = job->start({mailText}, /*secretOnly=*/false)) {
qCDebug(KLEOPATRA_LOG) << __func__ << "Error: Starting" << job << "for" << mailText << "failed with" << Formatting::errorAsString(err);
} else {
mLocateJob = job;
qCDebug(KLEOPATRA_LOG) << __func__ << "Started" << job << "for" << mailText;
}
}
void CertificateLineEdit::Private::onLocateJobResult(QGpgME::Job *job, const QString &email, const KeyListResult &result, const std::vector<GpgME::Key> &keys)
{
if (mLocateJob != job) {
qCDebug(KLEOPATRA_LOG) << __func__ << "Ignoring outdated finished" << job << "for" << email;
return;
}
qCDebug(KLEOPATRA_LOG) << __func__ << job << "for" << email << "finished with" << Formatting::errorAsString(result.error()) << "and keys" << keys;
mLocateJob.clear();
if (!keys.empty() && !keys.front().isNull()) {
KeyCache::mutableInstance()->insert(keys.front());
// inserting the key implicitly triggers an update
} else {
// explicitly trigger an update to display "no key" error
updateKey(CursorPositioning::MoveToStart);
}
}
void CertificateLineEdit::Private::updateKey(CursorPositioning positioning)
{
static const _detail::ByFingerprint<std::equal_to> keysHaveSameFingerprint;
const auto mailText = ui.lineEdit.text().trimmed();
auto newKey = Key();
auto newGroup = KeyGroup();
auto newUserId = UserID();
if (mailText.isEmpty()) {
mStatus = Status::Empty;
} else {
mFilterModel->setFilterRegularExpression(QRegularExpression::escape(mailText));
if (mFilterModel->rowCount() > 1) {
// keep current key, user id, or group if they still match
if (!mKey.isNull()) {
for (int row = 0; row < mFilterModel->rowCount(); ++row) {
const QModelIndex index = mFilterModel->index(row, 0);
Key key = mFilterModel->key(index);
if (!key.isNull() && keysHaveSameFingerprint(key, mKey)) {
newKey = mKey;
break;
}
}
} else if (!mGroup.isNull()) {
newGroup = mGroup;
for (int row = 0; row < mFilterModel->rowCount(); ++row) {
const QModelIndex index = mFilterModel->index(row, 0);
KeyGroup group = mFilterModel->group(index);
if (!group.isNull() && group.source() == mGroup.source() && group.id() == mGroup.id()) {
newGroup = mGroup;
break;
}
}
} else if (!mUserId.isNull()) {
for (int row = 0; row < mFilterModel->rowCount(); ++row) {
const QModelIndex index = mFilterModel->index(row, 0);
UserID userId = index.data(KeyList::UserIDRole).value<UserID>();
if (!userId.isNull() && keysHaveSameFingerprint(userId.parent(), mUserId.parent()) && !strcmp(userId.id(), mUserId.id())) {
newUserId = mUserId;
}
}
}
if (newKey.isNull() && newGroup.isNull() && newUserId.isNull()) {
mStatus = Status::Ambiguous;
}
} else if (mFilterModel->rowCount() == 1) {
const auto index = mFilterModel->index(0, 0);
newUserId = mFilterModel->data(index, KeyList::UserIDRole).value<UserID>();
if (newUserId.isNull()) {
newKey = mFilterModel->data(index, KeyList::KeyRole).value<Key>();
}
newGroup = mFilterModel->data(index, KeyList::GroupRole).value<KeyGroup>();
Q_ASSERT(!newKey.isNull() || !newGroup.isNull() || !newUserId.isNull());
if (newKey.isNull() && newGroup.isNull() && newUserId.isNull()) {
mStatus = Status::None;
}
} else {
if (!mUserId.isNull() && (mUserId.isRevoked() || mUserId.parent().isRevoked())) {
mStatus = Status::Revoked;
} else if (!mUserId.isNull() && mUserId.parent().isExpired()) {
mStatus = Status::Expired;
} else {
mStatus = Status::None;
}
}
}
mKey = newKey;
mGroup = newGroup;
mUserId = newUserId;
+ using namespace Kleo::Formatting;
if (!mKey.isNull()) {
/* FIXME: This needs to be solved by a multiple UID supporting model */
mStatus = Status::Success;
- ui.lineEdit.setToolTip(Formatting::toolTip(mKey, Formatting::ToolTipOption::AllOptions));
+ ui.lineEdit.setToolTip(Formatting::toolTip(mKey, Validity | Issuer | Subject | Fingerprint | ExpiryDates | UserIDs));
if (!mEditingInProgress) {
setTextWithBlockedSignals(Formatting::summaryLine(mKey), positioning);
}
} else if (!mGroup.isNull()) {
mStatus = Status::Success;
- ui.lineEdit.setToolTip(Formatting::toolTip(mGroup, Formatting::ToolTipOption::AllOptions));
+ ui.lineEdit.setToolTip(Formatting::toolTip(mGroup, Validity | Issuer | Subject | Fingerprint | ExpiryDates | UserIDs));
if (!mEditingInProgress) {
setTextWithBlockedSignals(Formatting::summaryLine(mGroup), positioning);
}
} else if (!mUserId.isNull()) {
mStatus = Status::Success;
- ui.lineEdit.setToolTip(Formatting::toolTip(mUserId, Formatting::ToolTipOption::AllOptions));
+ ui.lineEdit.setToolTip(Formatting::toolTip(mUserId, Validity | Issuer | Subject | Fingerprint | ExpiryDates | UserIDs));
if (!mEditingInProgress) {
setTextWithBlockedSignals(Formatting::summaryLine(mUserId), positioning);
}
} else {
ui.lineEdit.setToolTip({});
}
mShowDetailsAction->setEnabled(mStatus == Status::Success);
updateStatusAction();
updateErrorLabel();
Q_EMIT q->keyChanged();
}
QString CertificateLineEdit::Private::errorMessage() const
{
switch (mStatus) {
case Status::Empty:
case Status::Success:
return {};
case Status::None:
return i18n("No matching certificates or groups found");
case Status::Ambiguous:
return i18n("Multiple matching certificates or groups found");
case Status::Expired:
return i18n("This certificate is expired");
case Status::Revoked:
return i18n("This certificate is revoked");
default:
qDebug(KLEOPATRA_LOG) << __func__ << "Invalid status:" << static_cast<int>(mStatus);
Q_ASSERT(!"Invalid status");
};
return {};
}
QIcon CertificateLineEdit::Private::statusIcon() const
{
switch (mStatus) {
case Status::Empty:
return QIcon::fromTheme(QStringLiteral("emblem-unavailable"));
case Status::Success:
if (!mKey.isNull()) {
return mIconProvider.icon(mKey);
} else if (!mGroup.isNull()) {
return mIconProvider.icon(mGroup);
} else if (!mUserId.isNull()) {
return mIconProvider.icon(mUserId);
} else {
qDebug(KLEOPATRA_LOG) << __func__ << "Success, but neither key, nor user id, or group.";
return {};
}
case Status::None:
case Status::Ambiguous:
if (mEditingInProgress || mLocateJob) {
return QIcon::fromTheme(QStringLiteral("emblem-question"));
} else {
return QIcon::fromTheme(QStringLiteral("emblem-error"));
}
case Status::Expired:
case Status::Revoked:
return QIcon::fromTheme(QStringLiteral("emblem-error"));
default:
qDebug(KLEOPATRA_LOG) << __func__ << "Invalid status:" << static_cast<int>(mStatus);
Q_ASSERT(!"Invalid status");
};
return {};
}
QString CertificateLineEdit::Private::statusToolTip() const
{
switch (mStatus) {
case Status::Empty:
return {};
case Status::Success:
if (!mUserId.isNull()) {
return Formatting::validity(mUserId);
}
if (!mKey.isNull()) {
return Formatting::validity(mKey.userID(0));
} else if (!mGroup.isNull()) {
return Formatting::validity(mGroup);
} else {
qDebug(KLEOPATRA_LOG) << __func__ << "Success, but neither key, nor user id, or group.";
return {};
}
case Status::None:
case Status::Ambiguous:
return errorMessage();
default:
qDebug(KLEOPATRA_LOG) << __func__ << "Invalid status:" << static_cast<int>(mStatus);
Q_ASSERT(!"Invalid status");
};
return {};
}
void CertificateLineEdit::Private::updateStatusAction()
{
mStatusAction->setIcon(statusIcon());
mStatusAction->setToolTip(statusToolTip());
}
namespace
{
QString decoratedError(const QString &text)
{
return text.isEmpty() ? QString() : i18nc("@info", "Error: %1", text);
}
}
void CertificateLineEdit::Private::updateErrorLabel()
{
const auto currentErrorMessage = ui.errorLabel.text();
const auto newErrorMessage = decoratedError(errorMessage());
if (newErrorMessage == currentErrorMessage) {
return;
}
if (currentErrorMessage.isEmpty() && (mEditingInProgress || mLocateJob)) {
// delay showing the error message until editing is finished, so that we
// do not annoy the user with an error message while they are still
// entering the recipient;
// on the other hand, we clear the error message immediately if it does
// not apply anymore and we update the error message immediately if it
// changed
return;
}
ui.errorLabel.setVisible(!newErrorMessage.isEmpty());
ui.errorLabel.setText(newErrorMessage);
updateAccessibleNameAndDescription();
}
void CertificateLineEdit::Private::setAccessibleName(const QString &s)
{
mAccessibleName = s;
updateAccessibleNameAndDescription();
}
void CertificateLineEdit::Private::updateAccessibleNameAndDescription()
{
// fall back to default accessible name if accessible name wasn't set explicitly
if (mAccessibleName.isEmpty()) {
mAccessibleName = getAccessibleName(&ui.lineEdit);
}
const bool errorShown = ui.errorLabel.isVisible();
// Qt does not support "described-by" relations (like WCAG's "aria-describedby" relationship attribute);
// emulate this by setting the error message as accessible description of the input field
const auto description = errorShown ? ui.errorLabel.text() : QString{};
if (ui.lineEdit.accessibleDescription() != description) {
ui.lineEdit.setAccessibleDescription(description);
}
// Qt does not support IA2's "invalid entry" state (like WCAG's "aria-invalid" state attribute);
// screen readers say something like "invalid data" if this state is set;
// emulate this by adding "invalid data" to the accessible name of the input field
const auto name = errorShown ? mAccessibleName + QLatin1StringView{", "} + invalidEntryText() //
: mAccessibleName;
if (ui.lineEdit.accessibleName() != name) {
ui.lineEdit.setAccessibleName(name);
}
}
Key CertificateLineEdit::key() const
{
if (isEnabled()) {
return d->mKey;
} else {
return Key();
}
}
KeyGroup CertificateLineEdit::group() const
{
if (isEnabled()) {
return d->mGroup;
} else {
return KeyGroup();
}
}
UserID CertificateLineEdit::userID() const
{
if (isEnabled()) {
return d->mUserId;
} else {
return UserID();
}
}
QString CertificateLineEdit::Private::text() const
{
return ui.lineEdit.text().trimmed();
}
QString CertificateLineEdit::text() const
{
return d->text();
}
void CertificateLineEdit::Private::setKey(const Key &key)
{
mKey = key;
mGroup = KeyGroup();
mUserId = UserID();
qCDebug(KLEOPATRA_LOG) << "Setting Key. " << Formatting::summaryLine(key);
// position cursor, so that that the start of the summary is visible
setTextWithBlockedSignals(Formatting::summaryLine(key), CursorPositioning::MoveToStart);
updateKey(CursorPositioning::MoveToStart);
}
void CertificateLineEdit::setKey(const Key &key)
{
d->setKey(key);
}
void CertificateLineEdit::Private::setUserID(const UserID &userID)
{
mUserId = userID;
mKey = Key();
mGroup = KeyGroup();
qCDebug(KLEOPATRA_LOG) << "Setting UserID. " << Formatting::summaryLine(userID);
// position cursor, so that the start of the summary is visible
setTextWithBlockedSignals(Formatting::summaryLine(userID), CursorPositioning::MoveToStart);
updateKey(CursorPositioning::MoveToStart);
}
void CertificateLineEdit::setUserID(const UserID &userID)
{
d->setUserID(userID);
}
void CertificateLineEdit::Private::setGroup(const KeyGroup &group)
{
mGroup = group;
mKey = Key();
mUserId = UserID();
const QString summary = Formatting::summaryLine(group);
qCDebug(KLEOPATRA_LOG) << "Setting KeyGroup. " << summary;
// position cursor, so that that the start of the summary is visible
setTextWithBlockedSignals(summary, CursorPositioning::MoveToStart);
updateKey(CursorPositioning::MoveToStart);
}
void CertificateLineEdit::setGroup(const KeyGroup &group)
{
d->setGroup(group);
}
bool CertificateLineEdit::isEmpty() const
{
return d->mStatus == Private::Status::Empty;
}
bool CertificateLineEdit::isEditingInProgress() const
{
return d->mEditingInProgress;
}
bool CertificateLineEdit::hasAcceptableInput() const
{
return d->mStatus == Private::Status::Empty //
|| d->mStatus == Private::Status::Success;
}
void CertificateLineEdit::Private::setKeyFilter(const std::shared_ptr<KeyFilter> &filter)
{
mFilter = filter;
mFilterModel->setKeyFilter(filter);
mCompleterFilterModel->setKeyFilter(mFilter);
updateKey(CursorPositioning::Default);
}
void CertificateLineEdit::setKeyFilter(const std::shared_ptr<KeyFilter> &filter)
{
d->setKeyFilter(filter);
}
void CertificateLineEdit::setAccessibleNameOfLineEdit(const QString &name)
{
d->setAccessibleName(name);
}
#include "certificatelineedit.moc"
#include "moc_certificatelineedit.cpp"
File Metadata
Details
Attached
Mime Type
text/x-diff
Expires
Mon, Dec 8, 7:22 AM (1 d, 11 h)
Storage Engine
local-disk
Storage Format
Raw Data
Storage Handle
23/e8/32d531dac5001dbd000632ff8269
Attached To
rKLEOPATRA Kleopatra
Event Timeline
Log In to Comment