diff --git a/src/crypto/gui/certificatelineedit.cpp b/src/crypto/gui/certificatelineedit.cpp index 5c82ab904..d1802197a 100644 --- a/src/crypto/gui/certificatelineedit.cpp +++ b/src/crypto/gui/certificatelineedit.cpp @@ -1,348 +1,391 @@ /* 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 g10 Code GmbH + SPDX-FileCopyrightText: 2021, 2022 g10 Code GmbH SPDX-FileContributor: Ingo Klöcker SPDX-License-Identifier: GPL-2.0-or-later */ #include "certificatelineedit.h" #include #include #include #include #include "kleopatra_debug.h" #include #include +#include #include #include #include #include #include #include #include #include +#include + 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(); if (!key.isNull()) { return Kleo::Formatting::iconForUid(key.userID(0)); } const auto group = KeyListSortFilterProxyModel::data(idx, KeyList::GroupRole).value(); if (!group.isNull()) { return QIcon::fromTheme(QStringLiteral("group")); } Q_ASSERT(!key.isNull() || !group.isNull()); return QVariant(); } default: return KeyListSortFilterProxyModel::data(index(idx.row(), KeyList::Summary), role); } } }; } // namespace -CertificateLineEdit::CertificateLineEdit(AbstractKeyListModel *model, - QWidget *parent, - KeyFilter *filter) - : QLineEdit(parent), - mFilterModel(new KeyListSortFilterProxyModel(this)), - mCompleterFilterModel(new CompletionProxyModel(this)), - mCompleter(new QCompleter(this)), - mFilter(std::shared_ptr(filter)), - mLineAction(new QAction(this)) +class CertificateLineEdit::Private +{ + CertificateLineEdit *q; + +public: + explicit Private(CertificateLineEdit *qq, AbstractKeyListModel *model, KeyFilter *filter); + + void setKeyFilter(const std::shared_ptr &filter); + + void updateKey(); + void editChanged(); + void editFinished(); + void checkLocate(); + +public: + GpgME::Key mKey; + KeyGroup mGroup; + +private: + KeyListSortFilterProxyModel *const mFilterModel; + KeyListSortFilterProxyModel *const mCompleterFilterModel; + QCompleter *mCompleter = nullptr; + std::shared_ptr mFilter; + bool mEditStarted = false; + bool mEditFinished = false; + QAction *const mLineAction; +}; + +CertificateLineEdit::Private::Private(CertificateLineEdit *qq, AbstractKeyListModel *model, KeyFilter *filter) + : q{qq} + , mFilterModel{new KeyListSortFilterProxyModel{qq}} + , mCompleterFilterModel{new CompletionProxyModel{qq}} + , mCompleter{new QCompleter{qq}} + , mFilter{std::shared_ptr{filter}} + , mLineAction{new QAction{qq}} { - setPlaceholderText(i18n("Please enter a name or email address...")); - setClearButtonEnabled(true); - addAction(mLineAction, QLineEdit::LeadingPosition); + q->setPlaceholderText(i18n("Please enter a name or email address...")); + q->setClearButtonEnabled(true); + q->addAction(mLineAction, QLineEdit::LeadingPosition); mCompleterFilterModel->setKeyFilter(mFilter); mCompleterFilterModel->setSourceModel(model); mCompleter->setModel(mCompleterFilterModel); mCompleter->setFilterMode(Qt::MatchContains); mCompleter->setCaseSensitivity(Qt::CaseInsensitive); - setCompleter(mCompleter); + q->setCompleter(mCompleter); mFilterModel->setSourceModel(model); mFilterModel->setFilterKeyColumn(KeyList::Summary); if (filter) { mFilterModel->setKeyFilter(mFilter); } connect(KeyCache::instance().get(), &Kleo::KeyCache::keyListingDone, - this, &CertificateLineEdit::updateKey); + q, [this]() { updateKey(); }); connect(KeyCache::instance().get(), &Kleo::KeyCache::groupUpdated, - this, [this] (const KeyGroup &group) { + q, [this](const KeyGroup &group) { if (!mGroup.isNull() && mGroup.source() == group.source() && mGroup.id() == group.id()) { - QSignalBlocker blocky(this); - setText(Formatting::summaryLine(group)); + QSignalBlocker blocky{q}; + q->setText(Formatting::summaryLine(group)); // queue the update to ensure that the model has been updated - QMetaObject::invokeMethod(this, &CertificateLineEdit::updateKey, Qt::QueuedConnection); + QMetaObject::invokeMethod(q, [this]() { updateKey(); }, Qt::QueuedConnection); } }); connect(KeyCache::instance().get(), &Kleo::KeyCache::groupRemoved, - this, [this] (const KeyGroup &group) { + q, [this](const KeyGroup &group) { if (!mGroup.isNull() && mGroup.source() == group.source() && mGroup.id() == group.id()) { mGroup = KeyGroup(); - QSignalBlocker blocky(this); - clear(); + QSignalBlocker blocky{q}; + q->clear(); // queue the update to ensure that the model has been updated - QMetaObject::invokeMethod(this, &CertificateLineEdit::updateKey, Qt::QueuedConnection); + QMetaObject::invokeMethod(q, [this]() { updateKey(); }, Qt::QueuedConnection); } }); - connect(this, &QLineEdit::editingFinished, - this, [this] () { + connect(q, &QLineEdit::editingFinished, + q, [this]() { // queue the call of editFinished() to ensure that QCompleter::activated is handled first - QMetaObject::invokeMethod(this, &CertificateLineEdit::editFinished, Qt::QueuedConnection); + QMetaObject::invokeMethod(q, [this]() { editFinished(); }, Qt::QueuedConnection); }); - connect(this, &QLineEdit::textChanged, - this, &CertificateLineEdit::editChanged); + connect(q, &QLineEdit::textChanged, + q, [this]() { editChanged(); }); connect(mLineAction, &QAction::triggered, - this, &CertificateLineEdit::dialogRequested); + q, &CertificateLineEdit::dialogRequested); connect(mCompleter, qOverload(&QCompleter::activated), - this, [this] (const QModelIndex &index) { + q, [this] (const QModelIndex &index) { Key key = mCompleter->completionModel()->data(index, KeyList::KeyRole).value(); auto group = mCompleter->completionModel()->data(index, KeyList::GroupRole).value(); if (!key.isNull()) { - setKey(key); + q->setKey(key); } else if (!group.isNull()) { - setGroup(group); + q->setGroup(group); } else { qCDebug(KLEOPATRA_LOG) << "Activated item is neither key nor group"; } }); updateKey(); +} +CertificateLineEdit::CertificateLineEdit(AbstractKeyListModel *model, + QWidget *parent, + KeyFilter *filter) + : QLineEdit(parent) + , d{new Private{this, model, filter}} +{ /* Take ownership of the model to prevent double deletion when the * filter models are deleted */ model->setParent(parent ? parent : this); } -void CertificateLineEdit::editChanged() +CertificateLineEdit::~CertificateLineEdit() = default; + +void CertificateLineEdit::Private::editChanged() { mEditFinished = false; updateKey(); if (!mEditStarted) { - Q_EMIT editingStarted(); + Q_EMIT q->editingStarted(); mEditStarted = true; } } -void CertificateLineEdit::editFinished() +void CertificateLineEdit::Private::editFinished() { mEditStarted = false; mEditFinished = true; updateKey(); - if (!key().isNull()) { - QSignalBlocker blocky{this}; - setText(Formatting::summaryLine(key())); - } else if (!group().isNull()) { - QSignalBlocker blocky{this}; - setText(Formatting::summaryLine(group())); + if (!q->key().isNull()) { + QSignalBlocker blocky{q}; + q->setText(Formatting::summaryLine(q->key())); + } else if (!q->group().isNull()) { + QSignalBlocker blocky{q}; + q->setText(Formatting::summaryLine(q->group())); } else { checkLocate(); } } -void CertificateLineEdit::checkLocate() +void CertificateLineEdit::Private::checkLocate() { - if (!key().isNull() || !group().isNull()) { + if (!q->key().isNull() || !q->group().isNull()) { // Already have a key or group return; } // Only check once per mailbox - const auto mailText = text(); + const auto mailText = q->text(); if (s_lookedUpKeys.contains(mailText)) { return; } s_lookedUpKeys << mailText; qCDebug(KLEOPATRA_LOG) << "Lookup job for" << mailText; QGpgME::KeyForMailboxJob *job = QGpgME::openpgp()->keyForMailboxJob(); job->start(mailText); } -void CertificateLineEdit::updateKey() +void CertificateLineEdit::Private::updateKey() { static const _detail::ByFingerprint keysHaveSameFingerprint; - const auto mailText = text(); + const auto mailText = q->text(); auto newKey = Key(); auto newGroup = KeyGroup(); if (mailText.isEmpty()) { mLineAction->setIcon(QIcon::fromTheme(QStringLiteral("resource-group-new"))); mLineAction->setToolTip(i18n("Open selection dialog.")); - setToolTip({}); + q->setToolTip({}); } else { mFilterModel->setFilterFixedString(mailText); if (mFilterModel->rowCount() > 1) { // keep current key 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; } } } if (newKey.isNull() && newGroup.isNull()) { if (mEditFinished) { mLineAction->setIcon(QIcon::fromTheme(QStringLiteral("question"))); mLineAction->setToolTip(i18n("Multiple matching certificates found")); - setToolTip(i18n("Multiple matching certificates found")); + q->setToolTip(i18n("Multiple matching certificates found")); } else { mLineAction->setIcon(QIcon::fromTheme(QStringLiteral("resource-group-new"))); mLineAction->setToolTip(i18n("Open selection dialog.")); - setToolTip({}); + q->setToolTip({}); } } } else if (mFilterModel->rowCount() == 1) { const auto index = mFilterModel->index(0, 0); newKey = mFilterModel->data(index, KeyList::KeyRole).value(); newGroup = mFilterModel->data(index, KeyList::GroupRole).value(); Q_ASSERT(!newKey.isNull() || !newGroup.isNull()); if (newKey.isNull() && newGroup.isNull()) { mLineAction->setIcon(QIcon::fromTheme(QStringLiteral("emblem-error"))); mLineAction->setToolTip(i18n("No matching certificates found.
Click to import a certificate.")); - setToolTip(i18n("No matching certificates found")); + q->setToolTip(i18n("No matching certificates found")); } } else { mLineAction->setIcon(QIcon::fromTheme(QStringLiteral("emblem-error"))); mLineAction->setToolTip(i18n("No matching certificates found.
Click to import a certificate.")); - setToolTip(i18n("No matching certificates found")); + q->setToolTip(i18n("No matching certificates found")); } } mKey = newKey; mGroup = newGroup; if (!mKey.isNull()) { /* FIXME: This needs to be solved by a multiple UID supporting model */ mLineAction->setIcon(Formatting::iconForUid(mKey.userID(0))); mLineAction->setToolTip(Formatting::validity(mKey.userID(0)) + QLatin1String("
") + i18n("Click for details.")); - setToolTip(Formatting::toolTip(mKey, Formatting::ToolTipOption::AllOptions)); + q->setToolTip(Formatting::toolTip(mKey, Formatting::ToolTipOption::AllOptions)); } else if (!mGroup.isNull()) { mLineAction->setIcon(Formatting::validityIcon(mGroup)); mLineAction->setToolTip(Formatting::validity(mGroup) + QLatin1String("
") + i18n("Click for details.")); - setToolTip(Formatting::toolTip(mGroup, Formatting::ToolTipOption::AllOptions)); + q->setToolTip(Formatting::toolTip(mGroup, Formatting::ToolTipOption::AllOptions)); } - Q_EMIT keyChanged(); + Q_EMIT q->keyChanged(); if (mailText.isEmpty()) { - Q_EMIT wantsRemoval(this); + Q_EMIT q->wantsRemoval(q); } } Key CertificateLineEdit::key() const { if (isEnabled()) { - return mKey; + return d->mKey; } else { return Key(); } } KeyGroup CertificateLineEdit::group() const { if (isEnabled()) { - return mGroup; + return d->mGroup; } else { return KeyGroup(); } } void CertificateLineEdit::setKey(const Key &key) { - mKey = key; - mGroup = KeyGroup(); - QSignalBlocker blocky(this); + d->mKey = key; + d->mGroup = KeyGroup(); + QSignalBlocker blocky{this}; qCDebug(KLEOPATRA_LOG) << "Setting Key. " << Formatting::summaryLine(key); setText(Formatting::summaryLine(key)); - updateKey(); + d->updateKey(); } void CertificateLineEdit::setGroup(const KeyGroup &group) { - mGroup = group; - mKey = Key(); - QSignalBlocker blocky(this); + d->mGroup = group; + d->mKey = Key(); + QSignalBlocker blocky{this}; const QString summary = Formatting::summaryLine(group); qCDebug(KLEOPATRA_LOG) << "Setting KeyGroup. " << summary; setText(summary); - updateKey(); + d->updateKey(); } bool CertificateLineEdit::isEmpty() const { return text().isEmpty(); } -void CertificateLineEdit::setKeyFilter(const std::shared_ptr &filter) +void CertificateLineEdit::Private::setKeyFilter(const std::shared_ptr &filter) { mFilter = filter; mFilterModel->setKeyFilter(filter); mCompleterFilterModel->setKeyFilter(mFilter); updateKey(); } +void CertificateLineEdit::setKeyFilter(const std::shared_ptr &filter) +{ + d->setKeyFilter(filter); +} + #include "certificatelineedit.moc" diff --git a/src/crypto/gui/certificatelineedit.h b/src/crypto/gui/certificatelineedit.h index 5fa321030..94330db02 100644 --- a/src/crypto/gui/certificatelineedit.h +++ b/src/crypto/gui/certificatelineedit.h @@ -1,105 +1,89 @@ /* crypto/gui/certificatelineedit.h 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 g10 Code GmbH + SPDX-FileCopyrightText: 2021, 2022 g10 Code GmbH SPDX-FileContributor: Ingo Klöcker SPDX-License-Identifier: GPL-2.0-or-later */ #pragma once -#include - #include -#include - #include -class QCompleter; -class QLabel; -class QAction; +namespace GpgME +{ +class Key; +} namespace Kleo { class AbstractKeyListModel; class KeyFilter; -class KeyListSortFilterProxyModel; +class KeyGroup; /** Line edit and completion based Certificate Selection Widget. * * Shows the status of the selection with a status label and icon. * * The widget will use a single line HBox Layout. For larger dialog * see certificateslectiondialog. */ class CertificateLineEdit: public QLineEdit { Q_OBJECT public: /** Create the certificate selection line. * * If parent is not NULL the model is not taken * over but the parent argument used as the parent of the model. * * @param model: The keylistmodel to use. * @param parent: The usual widget parent. * @param filter: The filters to use. See certificateselectiondialog. */ - CertificateLineEdit(AbstractKeyListModel *model, - QWidget *parent = nullptr, - KeyFilter *filter = nullptr); + explicit CertificateLineEdit(AbstractKeyListModel *model, + QWidget *parent = nullptr, + KeyFilter *filter = nullptr); + + ~CertificateLineEdit() override; /** Get the selected key */ GpgME::Key key() const; KeyGroup group() const; /** Check if the text is empty */ bool isEmpty() const; /** Set the preselected Key for this widget. */ void setKey(const GpgME::Key &key); /** Set the preselected group for this widget. */ void setGroup(const KeyGroup &group); /** Set the used keyfilter. */ void setKeyFilter(const std::shared_ptr &filter); Q_SIGNALS: /** Emitted when the selected key changed. */ void keyChanged(); /** Emitted when the entry is empty and editing is finished. */ void wantsRemoval(CertificateLineEdit *w); /** Emitted when the entry is no longer empty. */ void editingStarted(); /** Emitted when the details dialog or the selection dialog is requested. */ void dialogRequested(); -private Q_SLOTS: - void updateKey(); - void editChanged(); - void editFinished(); - void checkLocate(); - private: - KeyListSortFilterProxyModel *const mFilterModel; - KeyListSortFilterProxyModel *const mCompleterFilterModel; - QCompleter *mCompleter = nullptr; - QLabel *mStatusLabel, - *mStatusIcon; - GpgME::Key mKey; - KeyGroup mGroup; - GpgME::Protocol mCurrentProto; - std::shared_ptr mFilter; - bool mEditStarted = false; - bool mEditFinished = false; - QAction *const mLineAction; + class Private; + std::unique_ptr const d; }; + }