Page Menu
Home
GnuPG
Search
Configure Global Search
Log In
Files
F35400959
No One
Temporary
Actions
View File
Edit File
Delete File
View Transforms
Subscribe
Mute Notifications
Award Token
Size
66 KB
Subscribers
None
View Options
diff --git a/src/conf/appearanceconfigwidget.cpp b/src/conf/appearanceconfigwidget.cpp
index bcd68db77..48877e56c 100644
--- a/src/conf/appearanceconfigwidget.cpp
+++ b/src/conf/appearanceconfigwidget.cpp
@@ -1,903 +1,904 @@
/*
This file is part of kleopatra, the KDE key manager
SPDX-FileCopyrightText: 2002, 2004, 2008 Klarälvdalens Datakonsult AB
SPDX-FileCopyrightText: 2002, 2003 Marc Mutz <mutz@kde.org>
SPDX-License-Identifier: GPL-2.0-or-later
*/
#include <config-kleopatra.h>
#include "appearanceconfigwidget.h"
#include <settings.h>
#include <Libkleo/DNAttributeOrderConfigWidget>
#include <Libkleo/DnAttributes>
#include <Libkleo/ExpiryCheckerConfig>
#include <Libkleo/KeyFilterManager>
#include <Libkleo/SystemInfo>
#include <KConfig>
#include <KConfigGroup>
#include <KIconDialog>
#include <KLocalization>
#include <KLocalizedString>
#include <KMessageWidget>
#include <KSeparator>
#include <QApplication>
#include <QCheckBox>
#include <QColor>
#include <QColorDialog>
#include <QFont>
#include <QFontDialog>
#include <QGridLayout>
#include <QIcon>
#include <QLabel>
#include <QListWidget>
#include <QRegularExpression>
#include <QSpinBox>
#include <QString>
#include <QVBoxLayout>
#include <algorithm>
using namespace Kleo;
using namespace Kleo::Config;
enum {
HasFontRole = Qt::UserRole + 0x1234, /*!< Records that the user has chosen completely different font (as opposed to italic/bold/strikeout) */
IconNameRole, /*!< Records the name of the icon (since QIcon won't give it out again, once set) */
MayChangeForegroundRole,
MayChangeBackgroundRole,
MayChangeFontRole,
MayChangeItalicRole,
MayChangeBoldRole,
MayChangeStrikeOutRole,
MayChangeIconRole,
StoredForegroundRole, /*!< Stores the actual configured foreground color */
StoredBackgroundRole, /*!< Stores the actual configured background color */
ConfigGroupRole,
EndDummy,
};
static QFont tryToFindFontFor(const QListWidgetItem *item)
{
if (item)
if (const QListWidget *const lw = item->listWidget()) {
return lw->font();
}
return QApplication::font("QListWidget");
}
static bool is(const QListWidgetItem *item, bool (QFont::*func)() const)
{
if (!item) {
return false;
}
const QVariant v = item->data(Qt::FontRole);
if (!v.isValid() || v.userType() != QMetaType::QFont) {
return false;
}
return (v.value<QFont>().*func)();
}
static bool is_italic(const QListWidgetItem *item)
{
return is(item, &QFont::italic);
}
static bool is_bold(const QListWidgetItem *item)
{
return is(item, &QFont::bold);
}
static bool is_strikeout(const QListWidgetItem *item)
{
return is(item, &QFont::strikeOut);
}
static void set(QListWidgetItem *item, bool on, void (QFont::*func)(bool))
{
if (!item) {
return;
}
const QVariant v = item->data(Qt::FontRole);
QFont font = v.isValid() && v.userType() == QMetaType::QFont ? v.value<QFont>() : tryToFindFontFor(item);
(font.*func)(on);
item->setData(Qt::FontRole, font);
}
static void set_italic(QListWidgetItem *item, bool on)
{
set(item, on, &QFont::setItalic);
}
static void set_bold(QListWidgetItem *item, bool on)
{
set(item, on, &QFont::setBold);
}
static void set_strikeout(QListWidgetItem *item, bool on)
{
set(item, on, &QFont::setStrikeOut);
}
static void apply_config(const KConfigGroup &group, QListWidgetItem *item)
{
if (!item) {
return;
}
const QString name = group.readEntry("Name");
item->setText(name.isEmpty() ? i18nc("Key filter without user-assigned name", "<unnamed>") : name);
item->setData(ConfigGroupRole, QVariant::fromValue(group));
const QColor fg = group.readEntry("foreground-color", QColor());
item->setData(StoredForegroundRole, fg.isValid() ? QBrush(fg) : QVariant());
if (!SystemInfo::isHighContrastModeActive()) {
item->setData(Qt::ForegroundRole, fg.isValid() ? QBrush(fg) : QVariant());
}
item->setData(MayChangeForegroundRole, !group.isEntryImmutable("foreground-color"));
const QColor bg = group.readEntry("background-color", QColor());
item->setData(StoredBackgroundRole, bg.isValid() ? QBrush(bg) : QVariant());
if (!SystemInfo::isHighContrastModeActive()) {
item->setData(Qt::BackgroundRole, bg.isValid() ? QBrush(bg) : QVariant());
}
item->setData(MayChangeBackgroundRole, !group.isEntryImmutable("background-color"));
const QFont defaultFont = tryToFindFontFor(item);
if (group.hasKey("font")) {
const QFont font = group.readEntry("font", defaultFont);
item->setData(Qt::FontRole, font != defaultFont ? font : QVariant());
item->setData(HasFontRole, font != defaultFont);
} else {
QFont font = defaultFont;
font.setStrikeOut(group.readEntry("font-strikeout", false));
font.setItalic(group.readEntry("font-italic", false));
font.setBold(group.readEntry("font-bold", false));
item->setData(Qt::FontRole, font);
item->setData(HasFontRole, false);
}
item->setData(MayChangeFontRole, !group.isEntryImmutable("font"));
item->setData(MayChangeItalicRole, !group.isEntryImmutable("font-italic"));
item->setData(MayChangeBoldRole, !group.isEntryImmutable("font-bold"));
item->setData(MayChangeStrikeOutRole, !group.isEntryImmutable("font-strikeout"));
const QString iconName = group.readEntry("icon");
item->setData(Qt::DecorationRole, iconName.isEmpty() ? QVariant() : QIcon::fromTheme(iconName));
item->setData(IconNameRole, iconName.isEmpty() ? QVariant() : iconName);
item->setData(MayChangeIconRole, !group.isEntryImmutable("icon"));
}
static void resetString(QListWidgetItem *item, int role, int allowRole, const char *key)
{
if (item && item->data(allowRole).toBool()) {
auto config = item->data(ConfigGroupRole).value<KConfigGroup>();
config.revertToDefault(key);
item->setData(role, config.readEntry(key, QString()));
}
}
static void resetColor(QListWidgetItem *item, int role, int allowRole, const char *key)
{
if (item && item->data(allowRole).toBool()) {
auto config = item->data(ConfigGroupRole).value<KConfigGroup>();
config.revertToDefault(key);
const auto value = config.readEntry(key, QColor{});
item->setData(role, value.isValid() ? value : QVariant());
}
}
static void erase_if_allowed(QListWidgetItem *item, const int allowRole[], size_t numAllowRoles)
{
if (!item) {
return;
}
for (unsigned int i = 0; i < numAllowRoles; ++i)
if (!item->data(allowRole[i]).toBool()) {
return;
}
auto config = item->data(ConfigGroupRole).value<KConfigGroup>();
config.revertToDefault("font");
config.revertToDefault("font-bold");
config.revertToDefault("font-strikeout");
config.revertToDefault("font-italic");
auto value = config.readEntry<QFont>("font", tryToFindFontFor(item));
value.setBold(config.readEntry("font-bold", false));
value.setStrikeOut(config.readEntry("font-strikeout", false));
value.setItalic(config.readEntry("font-italic", false));
item->setData(Qt::FontRole, value);
}
static void set_default_appearance(QListWidgetItem *item)
{
if (!item) {
return;
}
if (!SystemInfo::isHighContrastModeActive()) {
resetColor(item, Qt::ForegroundRole, MayChangeForegroundRole, "foreground-color");
resetColor(item, Qt::BackgroundRole, MayChangeBackgroundRole, "background-color");
}
resetColor(item, StoredForegroundRole, MayChangeForegroundRole, "foreground-color");
resetColor(item, StoredBackgroundRole, MayChangeBackgroundRole, "background-color");
resetString(item, IconNameRole, MayChangeIconRole, "icon");
item->setData(Qt::DecorationRole, QIcon::fromTheme(item->data(ConfigGroupRole).value<KConfigGroup>().readEntry("icon", QString())));
static const int fontAllowRoles[] = {
MayChangeFontRole,
MayChangeItalicRole,
MayChangeBoldRole,
MayChangeStrikeOutRole,
};
erase_if_allowed(item, fontAllowRoles, sizeof(fontAllowRoles) / sizeof(int));
}
static void writeOrDelete(KConfigGroup &group, const char *key, const QVariant &value)
{
if (value.isValid()) {
group.writeEntry(key, value);
} else {
group.revertToDefault(key);
}
}
static QVariant brush2color(const QVariant &v)
{
if (v.isValid()) {
if (v.userType() == QMetaType::QColor) {
return v;
} else if (v.userType() == QMetaType::QBrush) {
return v.value<QBrush>().color();
}
}
return QVariant();
}
static void save_to_config(const QListWidgetItem *item, KConfigGroup &group)
{
if (!item) {
return;
}
writeOrDelete(group, "foreground-color", brush2color(item->data(StoredForegroundRole)));
writeOrDelete(group, "background-color", brush2color(item->data(StoredBackgroundRole)));
writeOrDelete(group, "icon", item->data(IconNameRole));
group.deleteEntry("font");
group.deleteEntry("font-strikeout");
group.deleteEntry("font-italic");
group.deleteEntry("font-bold");
if (item->data(HasFontRole).toBool()) {
writeOrDelete(group, "font", item->data(Qt::FontRole));
return;
}
if (is_strikeout(item)) {
group.writeEntry("font-strikeout", true);
}
if (is_italic(item)) {
group.writeEntry("font-italic", true);
}
if (is_bold(item)) {
group.writeEntry("font-bold", true);
}
}
static void kiosk_enable(QWidget *w, const QListWidgetItem *item, int allowRole)
{
if (!w) {
return;
}
if (item && !item->data(allowRole).toBool()) {
w->setEnabled(false);
w->setToolTip(i18nc("@info:tooltip", "This parameter has been locked down by the system administrator."));
} else {
w->setEnabled(item);
w->setToolTip(QString());
}
}
class Ui_AppearanceConfigWidget
{
public:
QTabWidget *tabWidget;
KMessageWidget *highContrastMsg;
QListWidget *categoriesLV;
QPushButton *iconButton;
QPushButton *foregroundButton;
QPushButton *backgroundButton;
QPushButton *fontButton;
QCheckBox *italicCB;
QCheckBox *boldCB;
QCheckBox *strikeoutCB;
QPushButton *defaultLookPB;
QCheckBox *tooltipValidityCheckBox;
QCheckBox *tooltipOwnerCheckBox;
QCheckBox *tooltipDetailsCheckBox;
QCheckBox *showExpirationCheckBox;
QSpinBox *ownCertificateThresholdSpinBox;
QSpinBox *otherCertificateThresholdSpinBox;
void setupUi(QWidget *parent)
{
if (parent->objectName().isEmpty())
parent->setObjectName(QString::fromUtf8("AppearanceConfigWidget"));
auto mainLayout = new QVBoxLayout{parent};
mainLayout->setContentsMargins({});
tabWidget = new QTabWidget(parent);
tabWidget->setDocumentMode(true);
tabWidget->setObjectName(QString::fromUtf8("tabWidget"));
{
auto tab = new QWidget{parent};
auto tabLayout = new QVBoxLayout{tab};
tabLayout->addWidget(new KSeparator{tab});
auto label = new QLabel{tab};
label->setText(i18nc("@info", "Show the following information in certificate list tooltips:"));
tabLayout->addWidget(label);
tooltipValidityCheckBox = new QCheckBox{i18nc("@option:check", "Show validity"), tab};
tabLayout->addWidget(tooltipValidityCheckBox);
tooltipOwnerCheckBox = new QCheckBox{i18nc("@option:check", "Show owner information"), tab};
tabLayout->addWidget(tooltipOwnerCheckBox);
tooltipDetailsCheckBox = new QCheckBox{i18nc("@option:check", "Show technical details"), tab};
tabLayout->addWidget(tooltipDetailsCheckBox);
tabLayout->addWidget(new KSeparator{tab});
showExpirationCheckBox = new QCheckBox{i18nc("@option:check", "Show upcoming certificate expiration"), tab};
tabLayout->addWidget(showExpirationCheckBox);
{
auto gridLayout = new QGridLayout;
const ExpiryCheckerConfig expiryConfig;
{
auto label = new QLabel{i18nc("@label:spinbox", "Threshold for own certificates:"), tab};
ownCertificateThresholdSpinBox = new QSpinBox{tab};
label->setBuddy(ownCertificateThresholdSpinBox);
const auto configItem = expiryConfig.ownKeyThresholdInDaysItem();
ownCertificateThresholdSpinBox->setMinimum(configItem->minValue().toInt());
ownCertificateThresholdSpinBox->setMaximum(configItem->maxValue().toInt());
ownCertificateThresholdSpinBox->setSpecialValueText(i18nc("@item never show expiry notification", "never"));
KLocalization::setupSpinBoxFormatString(ownCertificateThresholdSpinBox, ki18ncp("@item:valuesuffix", "%v day", "%v days"));
ownCertificateThresholdSpinBox->setToolTip(
i18nc("@info:tooltip", "Select the number of days you want to be warned in advance, if your own certificate is about to expire soon."));
gridLayout->addWidget(label, 0, 0);
gridLayout->addWidget(ownCertificateThresholdSpinBox, 0, 1);
}
{
auto label = new QLabel{i18nc("@label:spinbox", "Threshold for other certificates:"), tab};
otherCertificateThresholdSpinBox = new QSpinBox{tab};
label->setBuddy(otherCertificateThresholdSpinBox);
const auto configItem = expiryConfig.otherKeyThresholdInDaysItem();
otherCertificateThresholdSpinBox->setMinimum(configItem->minValue().toInt());
otherCertificateThresholdSpinBox->setMaximum(configItem->maxValue().toInt());
otherCertificateThresholdSpinBox->setSpecialValueText(i18nc("@item never show expiry notification", "never"));
KLocalization::setupSpinBoxFormatString(otherCertificateThresholdSpinBox, ki18ncp("@item:valuesuffix", "%v day", "%v days"));
otherCertificateThresholdSpinBox->setToolTip(
i18nc("@info:tooltip",
"Select the number of days you want to be warned in advance, if another person's certificate is about to expire soon."));
gridLayout->addWidget(label, 1, 0);
gridLayout->addWidget(otherCertificateThresholdSpinBox, 1, 1);
}
gridLayout->setColumnStretch(2, 1);
tabLayout->addLayout(gridLayout);
}
tabLayout->addStretch(1);
tabWidget->addTab(tab, i18nc("@title:tab", "General"));
}
auto tab_2 = new QWidget();
tab_2->setObjectName(QString::fromUtf8("tab_2"));
auto gridLayout = new QGridLayout(tab_2);
gridLayout->setObjectName(QString::fromUtf8("gridLayout"));
highContrastMsg = new KMessageWidget(tab_2);
highContrastMsg->setObjectName(QString::fromUtf8("highContrastMsg"));
gridLayout->addWidget(highContrastMsg, 0, 0, 1, 2);
categoriesLV = new QListWidget(tab_2);
categoriesLV->setObjectName(QString::fromUtf8("categoriesLV"));
+ categoriesLV->setAccessibleName(i18nc("@label", "Categories"));
gridLayout->addWidget(categoriesLV, 1, 0, 1, 1);
auto vboxLayout = new QVBoxLayout();
vboxLayout->setObjectName(QString::fromUtf8("vboxLayout"));
iconButton = new QPushButton(tab_2);
iconButton->setText(i18nc("@action:button", "Set Icon..."));
iconButton->setObjectName(QString::fromUtf8("iconButton"));
iconButton->setEnabled(false);
vboxLayout->addWidget(iconButton);
foregroundButton = new QPushButton(tab_2);
foregroundButton->setText(i18nc("@action:button", "Set Text Color..."));
foregroundButton->setObjectName(QString::fromUtf8("foregroundButton"));
foregroundButton->setEnabled(false);
vboxLayout->addWidget(foregroundButton);
backgroundButton = new QPushButton(tab_2);
backgroundButton->setText(i18nc("@action:button", "Set Background Color..."));
backgroundButton->setObjectName(QString::fromUtf8("backgroundButton"));
backgroundButton->setEnabled(false);
vboxLayout->addWidget(backgroundButton);
fontButton = new QPushButton(tab_2);
fontButton->setText(i18nc("@action:button", "Set Font..."));
fontButton->setObjectName(QString::fromUtf8("fontButton"));
fontButton->setEnabled(false);
vboxLayout->addWidget(fontButton);
italicCB = new QCheckBox(tab_2);
italicCB->setText(i18nc("@option:check", "Italic"));
italicCB->setObjectName(QString::fromUtf8("italicCB"));
italicCB->setEnabled(false);
vboxLayout->addWidget(italicCB);
boldCB = new QCheckBox(tab_2);
boldCB->setText(i18nc("@option:check", "Bold"));
boldCB->setObjectName(QString::fromUtf8("boldCB"));
boldCB->setEnabled(false);
vboxLayout->addWidget(boldCB);
strikeoutCB = new QCheckBox(tab_2);
strikeoutCB->setText(i18nc("@option:check", "Strikeout"));
strikeoutCB->setObjectName(QString::fromUtf8("strikeoutCB"));
strikeoutCB->setEnabled(false);
vboxLayout->addWidget(strikeoutCB);
vboxLayout->addStretch(1);
defaultLookPB = new QPushButton(tab_2);
defaultLookPB->setText(i18nc("@action:button", "Default Appearance"));
defaultLookPB->setObjectName(QString::fromUtf8("defaultLookPB"));
defaultLookPB->setEnabled(false);
vboxLayout->addWidget(defaultLookPB);
gridLayout->addLayout(vboxLayout, 1, 1, 1, 1);
tabWidget->addTab(tab_2, i18nc("@title:tab", "Certificate Categories"));
mainLayout->addWidget(tabWidget);
}
};
class AppearanceConfigWidget::Private : public Ui_AppearanceConfigWidget
{
friend class ::Kleo::Config::AppearanceConfigWidget;
AppearanceConfigWidget *const q;
public:
explicit Private(AppearanceConfigWidget *qq)
: Ui_AppearanceConfigWidget()
, q(qq)
{
setupUi(q);
if (QLayout *const l = q->layout()) {
l->setContentsMargins(0, 0, 0, 0);
}
highContrastMsg->setVisible(SystemInfo::isHighContrastModeActive());
highContrastMsg->setMessageType(KMessageWidget::Warning);
highContrastMsg->setIcon(q->style()->standardIcon(QStyle::SP_MessageBoxWarning, nullptr, q));
highContrastMsg->setText(i18n("The preview of colors is disabled because high-contrast mode is active."));
highContrastMsg->setCloseButtonVisible(false);
if (Kleo::Settings{}.cmsEnabled()) {
auto w = new QWidget;
dnOrderWidget = new DNAttributeOrderConfigWidget{w};
dnOrderWidget->setObjectName(QLatin1StringView("dnOrderWidget"));
(new QVBoxLayout(w))->addWidget(dnOrderWidget);
tabWidget->addTab(w, i18n("DN-Attribute Order"));
connect(dnOrderWidget, &DNAttributeOrderConfigWidget::changed, q, &AppearanceConfigWidget::changed);
}
connect(iconButton, SIGNAL(clicked()), q, SLOT(slotIconClicked()));
#ifndef QT_NO_COLORDIALOG
connect(foregroundButton, SIGNAL(clicked()), q, SLOT(slotForegroundClicked()));
connect(backgroundButton, SIGNAL(clicked()), q, SLOT(slotBackgroundClicked()));
#else
foregroundButton->hide();
backgroundButton->hide();
#endif
#ifndef QT_NO_FONTDIALOG
connect(fontButton, SIGNAL(clicked()), q, SLOT(slotFontClicked()));
#else
fontButton->hide();
#endif
auto emitChanged = [this]() {
Q_EMIT q->changed();
};
connect(categoriesLV, SIGNAL(itemSelectionChanged()), q, SLOT(slotSelectionChanged()));
connect(defaultLookPB, SIGNAL(clicked()), q, SLOT(slotDefaultClicked()));
connect(italicCB, SIGNAL(toggled(bool)), q, SLOT(slotItalicToggled(bool)));
connect(boldCB, SIGNAL(toggled(bool)), q, SLOT(slotBoldToggled(bool)));
connect(strikeoutCB, SIGNAL(toggled(bool)), q, SLOT(slotStrikeOutToggled(bool)));
connect(tooltipValidityCheckBox, SIGNAL(toggled(bool)), q, SLOT(slotTooltipValidityChanged(bool)));
connect(tooltipOwnerCheckBox, SIGNAL(toggled(bool)), q, SLOT(slotTooltipOwnerChanged(bool)));
connect(tooltipDetailsCheckBox, SIGNAL(toggled(bool)), q, SLOT(slotTooltipDetailsChanged(bool)));
connect(showExpirationCheckBox, &QCheckBox::toggled, q, emitChanged);
connect(ownCertificateThresholdSpinBox, &QSpinBox::valueChanged, q, emitChanged);
connect(otherCertificateThresholdSpinBox, &QSpinBox::valueChanged, q, emitChanged);
}
private:
void enableDisableActions(QListWidgetItem *item);
QListWidgetItem *selectedItem() const;
private:
void slotIconClicked();
#ifndef QT_NO_COLORDIALOG
void slotForegroundClicked();
void slotBackgroundClicked();
#endif
#ifndef QT_NO_FONTDIALOG
void slotFontClicked();
#endif
void slotSelectionChanged();
void slotDefaultClicked();
void slotItalicToggled(bool);
void slotBoldToggled(bool);
void slotStrikeOutToggled(bool);
void slotTooltipValidityChanged(bool);
void slotTooltipOwnerChanged(bool);
void slotTooltipDetailsChanged(bool);
private:
Kleo::DNAttributeOrderConfigWidget *dnOrderWidget = nullptr;
};
AppearanceConfigWidget::AppearanceConfigWidget(QWidget *p, Qt::WindowFlags f)
: QWidget(p, f)
, d(new Private(this))
{
// load();
}
AppearanceConfigWidget::~AppearanceConfigWidget()
{
}
void AppearanceConfigWidget::Private::slotSelectionChanged()
{
enableDisableActions(selectedItem());
}
QListWidgetItem *AppearanceConfigWidget::Private::selectedItem() const
{
const QList<QListWidgetItem *> items = categoriesLV->selectedItems();
return items.empty() ? nullptr : items.front();
}
void AppearanceConfigWidget::Private::enableDisableActions(QListWidgetItem *item)
{
kiosk_enable(iconButton, item, MayChangeIconRole);
#ifndef QT_NO_COLORDIALOG
kiosk_enable(foregroundButton, item, MayChangeForegroundRole);
kiosk_enable(backgroundButton, item, MayChangeBackgroundRole);
#endif
#ifndef QT_NO_FONTDIALOG
kiosk_enable(fontButton, item, MayChangeFontRole);
#endif
kiosk_enable(italicCB, item, MayChangeItalicRole);
kiosk_enable(boldCB, item, MayChangeBoldRole);
kiosk_enable(strikeoutCB, item, MayChangeStrikeOutRole);
defaultLookPB->setEnabled(item);
italicCB->setChecked(is_italic(item));
boldCB->setChecked(is_bold(item));
strikeoutCB->setChecked(is_strikeout(item));
}
void AppearanceConfigWidget::Private::slotDefaultClicked()
{
QListWidgetItem *const item = selectedItem();
if (!item) {
return;
}
set_default_appearance(item);
enableDisableActions(item);
Q_EMIT q->changed();
}
void AppearanceConfigWidget::defaults()
{
// use temporary KConfigSkeleton instances for (re)setting the values to the defaults;
// the setters respect the immutability of the individual settings, so that we don't have
// to check this ourselves
Settings settings;
settings.setShowExpiryNotifications(settings.findItem(QStringLiteral("ShowExpiryNotifications"))->getDefault().toBool());
d->showExpirationCheckBox->setChecked(settings.showExpiryNotifications());
{
ExpiryCheckerConfig expiryConfig;
expiryConfig.setOwnKeyThresholdInDays(expiryConfig.ownKeyThresholdInDaysItem()->getDefault().toInt());
d->ownCertificateThresholdSpinBox->setValue(expiryConfig.ownKeyThresholdInDays());
expiryConfig.setOtherKeyThresholdInDays(expiryConfig.otherKeyThresholdInDaysItem()->getDefault().toInt());
d->otherCertificateThresholdSpinBox->setValue(expiryConfig.otherKeyThresholdInDays());
}
// This simply means "default look for every category"
for (int i = 0, end = d->categoriesLV->count(); i != end; ++i) {
set_default_appearance(d->categoriesLV->item(i));
}
settings.setShowValidity(settings.findItem(QStringLiteral("ShowValidity"))->getDefault().toBool());
d->tooltipValidityCheckBox->setChecked(settings.showValidity());
settings.setShowOwnerInformation(settings.findItem(QStringLiteral("ShowOwnerInformation"))->getDefault().toBool());
d->tooltipOwnerCheckBox->setChecked(settings.showOwnerInformation());
settings.setShowCertificateDetails(settings.findItem(QStringLiteral("ShowCertificateDetails"))->getDefault().toBool());
d->tooltipDetailsCheckBox->setChecked(settings.showCertificateDetails());
if (d->dnOrderWidget) {
if (!settings.isImmutable(QStringLiteral("AttributeOrder"))) {
d->dnOrderWidget->setAttributeOrder(DNAttributes::defaultOrder());
}
}
Q_EMIT changed();
}
bool greaterThanBySpecificity(const QString &lhs, const QString &rhs)
{
const auto lFilter = KeyFilterManager::instance()->keyFilterByID(lhs);
const auto rFilter = KeyFilterManager::instance()->keyFilterByID(rhs);
if (!lFilter) {
return false;
}
if (!rFilter) {
return true;
}
return lFilter->specificity() > rFilter->specificity();
}
void AppearanceConfigWidget::load()
{
const Settings settings;
d->showExpirationCheckBox->setChecked(settings.showExpiryNotifications());
d->showExpirationCheckBox->setEnabled(!settings.isImmutable(QStringLiteral("ShowExpiryNotifications")));
{
const ExpiryCheckerConfig expiryConfig;
d->ownCertificateThresholdSpinBox->setValue(expiryConfig.ownKeyThresholdInDays());
d->ownCertificateThresholdSpinBox->setEnabled(!expiryConfig.ownKeyThresholdInDaysItem()->isImmutable());
d->otherCertificateThresholdSpinBox->setValue(expiryConfig.otherKeyThresholdInDays());
d->otherCertificateThresholdSpinBox->setEnabled(!expiryConfig.otherKeyThresholdInDaysItem()->isImmutable());
}
if (d->dnOrderWidget) {
d->dnOrderWidget->setAttributeOrder(DNAttributes::order());
d->dnOrderWidget->setEnabled(!settings.isImmutable(QStringLiteral("AttributeOrder")));
}
d->categoriesLV->clear();
KSharedConfigPtr config = KSharedConfig::openConfig(QStringLiteral("libkleopatrarc"));
if (!config) {
return;
}
QStringList groups = config->groupList().filter(QRegularExpression(QStringLiteral("^Key Filter #\\d+$")));
std::ranges::stable_sort(groups, greaterThanBySpecificity);
for (const QString &group : groups) {
const KConfigGroup configGroup{config, group};
const bool isCmsSpecificKeyFilter = !configGroup.readEntry("is-openpgp-key", true);
auto item = new QListWidgetItem{d->categoriesLV};
// hide CMS-specific filters if CMS is disabled; we hide those filters
// instead of skipping them, so that they are not removed on save
item->setHidden(isCmsSpecificKeyFilter && !Kleo::Settings{}.cmsEnabled());
apply_config(configGroup, item);
}
d->tooltipValidityCheckBox->setChecked(settings.showValidity());
d->tooltipValidityCheckBox->setEnabled(!settings.isImmutable(QStringLiteral("ShowValidity")));
d->tooltipOwnerCheckBox->setChecked(settings.showOwnerInformation());
d->tooltipOwnerCheckBox->setEnabled(!settings.isImmutable(QStringLiteral("ShowOwnerInformation")));
d->tooltipDetailsCheckBox->setChecked(settings.showCertificateDetails());
d->tooltipDetailsCheckBox->setEnabled(!settings.isImmutable(QStringLiteral("ShowCertificateDetails")));
}
void AppearanceConfigWidget::save()
{
Settings settings;
settings.setShowExpiryNotifications(d->showExpirationCheckBox->isChecked());
if (d->dnOrderWidget) {
settings.setAttributeOrder(d->dnOrderWidget->attributeOrder());
DNAttributes::setOrder(settings.attributeOrder());
}
{
ExpiryCheckerConfig expiryConfig;
expiryConfig.setOwnKeyThresholdInDays(d->ownCertificateThresholdSpinBox->value());
expiryConfig.setOtherKeyThresholdInDays(d->otherCertificateThresholdSpinBox->value());
expiryConfig.save();
}
settings.setShowValidity(d->tooltipValidityCheckBox->isChecked());
settings.setShowOwnerInformation(d->tooltipOwnerCheckBox->isChecked());
settings.setShowCertificateDetails(d->tooltipDetailsCheckBox->isChecked());
settings.save();
KSharedConfigPtr config = KSharedConfig::openConfig(QStringLiteral("libkleopatrarc"));
if (!config) {
return;
}
// We know (assume) that the groups in the config object haven't changed,
// so we just iterate over them and over the listviewitems, and map one-to-one.
QStringList groups = config->groupList().filter(QRegularExpression(QStringLiteral("^Key Filter #\\d+$")));
std::ranges::stable_sort(groups, greaterThanBySpecificity);
#if 0
if (groups.isEmpty()) {
// If we created the default categories ourselves just now, then we need to make up their list
Q3ListViewItemIterator lvit(categoriesLV);
for (; lvit.current(); ++lvit) {
groups << lvit.current()->text(0);
}
}
#endif
for (int i = 0, end = std::min<int>(groups.size(), d->categoriesLV->count()); i != end; ++i) {
const QListWidgetItem *const item = d->categoriesLV->item(i);
Q_ASSERT(item);
KConfigGroup group(config, groups[i]);
save_to_config(item, group);
}
config->sync();
KeyFilterManager::instance()->reload();
}
void AppearanceConfigWidget::Private::slotIconClicked()
{
QListWidgetItem *const item = selectedItem();
if (!item) {
return;
}
const QString iconName = KIconDialog::getIcon(/* repeating default arguments begin */
KIconLoader::Desktop,
KIconLoader::Application,
false,
0,
false,
/* repeating default arguments end */
q);
if (iconName.isEmpty()) {
return;
}
item->setIcon(QIcon::fromTheme(iconName));
item->setData(IconNameRole, iconName);
Q_EMIT q->changed();
}
#ifndef QT_NO_COLORDIALOG
void AppearanceConfigWidget::Private::slotForegroundClicked()
{
QListWidgetItem *const item = selectedItem();
if (!item) {
return;
}
const QVariant v = brush2color(item->data(StoredForegroundRole));
const QColor initial = v.isValid() ? v.value<QColor>() : categoriesLV->palette().color(QPalette::Normal, QPalette::Text);
const QColor c = QColorDialog::getColor(initial, q);
if (c.isValid()) {
item->setData(StoredForegroundRole, QBrush(c));
if (!SystemInfo::isHighContrastModeActive()) {
item->setData(Qt::ForegroundRole, QBrush(c));
}
Q_EMIT q->changed();
}
}
void AppearanceConfigWidget::Private::slotBackgroundClicked()
{
QListWidgetItem *const item = selectedItem();
if (!item) {
return;
}
const QVariant v = brush2color(item->data(StoredBackgroundRole));
const QColor initial = v.isValid() ? v.value<QColor>() : categoriesLV->palette().color(QPalette::Normal, QPalette::Base);
const QColor c = QColorDialog::getColor(initial, q);
if (c.isValid()) {
item->setData(StoredBackgroundRole, QBrush(c));
if (!SystemInfo::isHighContrastModeActive()) {
item->setData(Qt::BackgroundRole, QBrush(c));
}
Q_EMIT q->changed();
}
}
#endif // QT_NO_COLORDIALOG
#ifndef QT_NO_FONTDIALOG
void AppearanceConfigWidget::Private::slotFontClicked()
{
QListWidgetItem *const item = selectedItem();
if (!item) {
return;
}
const QVariant v = item->data(Qt::FontRole);
bool ok = false;
const QFont defaultFont = tryToFindFontFor(item);
const QFont initial = v.isValid() && v.userType() == QMetaType::QFont ? v.value<QFont>() : defaultFont;
QFont f = QFontDialog::getFont(&ok, initial, q);
if (!ok) {
return;
}
// disallow circumventing KIOSK:
if (!item->data(MayChangeItalicRole).toBool()) {
f.setItalic(initial.italic());
}
if (!item->data(MayChangeBoldRole).toBool()) {
f.setBold(initial.bold());
}
if (!item->data(MayChangeStrikeOutRole).toBool()) {
f.setStrikeOut(initial.strikeOut());
}
item->setData(Qt::FontRole, f != defaultFont ? f : QVariant());
item->setData(HasFontRole, true);
Q_EMIT q->changed();
}
#endif // QT_NO_FONTDIALOG
void AppearanceConfigWidget::Private::slotItalicToggled(bool on)
{
set_italic(selectedItem(), on);
Q_EMIT q->changed();
}
void AppearanceConfigWidget::Private::slotBoldToggled(bool on)
{
set_bold(selectedItem(), on);
Q_EMIT q->changed();
}
void AppearanceConfigWidget::Private::slotStrikeOutToggled(bool on)
{
set_strikeout(selectedItem(), on);
Q_EMIT q->changed();
}
void AppearanceConfigWidget::Private::slotTooltipValidityChanged(bool)
{
Q_EMIT q->changed();
}
void AppearanceConfigWidget::Private::slotTooltipOwnerChanged(bool)
{
Q_EMIT q->changed();
}
void AppearanceConfigWidget::Private::slotTooltipDetailsChanged(bool)
{
Q_EMIT q->changed();
}
#include "moc_appearanceconfigwidget.cpp"
diff --git a/src/dialogs/deletecertificatesdialog.cpp b/src/dialogs/deletecertificatesdialog.cpp
index b231a8298..8ea3b5dda 100644
--- a/src/dialogs/deletecertificatesdialog.cpp
+++ b/src/dialogs/deletecertificatesdialog.cpp
@@ -1,293 +1,297 @@
/*
This file is part of Kleopatra, the KDE keymanager
SPDX-FileCopyrightText: 2009 Klarälvdalens Datakonsult AB
SPDX-License-Identifier: GPL-2.0-or-later
*/
#include <config-kleopatra.h>
#include "deletecertificatesdialog.h"
#include <view/keytreeview.h>
#include <Libkleo/Algorithm>
#include <Libkleo/Formatting>
#include <Libkleo/KeyCache>
#include <Libkleo/KeyListModel>
#include <Libkleo/Stl_Util>
#include "kleopatra_debug.h"
#include <KConfigGroup>
#include <KLocalizedString>
#include <KMessageBox>
#include <KSharedConfig>
#include <KStandardGuiItem>
#include <QCursor>
#include <QDialogButtonBox>
#include <QLabel>
#include <QListWidget>
#include <QPushButton>
#include <QScreen>
#include <QTreeView>
#include <QVBoxLayout>
#include <QWhatsThis>
#include <gpgme++/key.h>
using namespace Kleo;
using namespace Kleo::Dialogs;
using namespace GpgME;
static int maxRecommendedWidth(QList<QListWidget *> widgets)
{
if (widgets.isEmpty()) {
return -1;
}
auto metrics = widgets[0]->fontMetrics();
auto maxWidth = -1;
for (const auto &widget : widgets) {
for (int i = 0; i < widget->count(); i++) {
auto width = metrics.boundingRect(widget->item(i)->text()).width();
if (width > maxWidth) {
maxWidth = width;
}
}
}
return std::min(widgets[0]->screen()->size().width(), maxWidth);
}
class DeleteCertificatesDialog::Private
{
friend class ::Kleo::Dialogs::DeleteCertificatesDialog;
DeleteCertificatesDialog *const q;
public:
explicit Private(DeleteCertificatesDialog *qq)
: q(qq)
, ui(q)
{
}
void readConfig()
{
KConfigGroup dialog(KSharedConfig::openStateConfig(), QStringLiteral("DeleteCertificatesDialog"));
const QSize size = dialog.readEntry("Size", QSize(600, 400));
if (size.isValid()) {
q->resize(size);
}
}
void writeConfig()
{
KConfigGroup dialog(KSharedConfig::openStateConfig(), QStringLiteral("DeleteCertificatesDialog"));
dialog.writeEntry("Size", q->size());
dialog.sync();
}
void checkGroups(const std::vector<Key> &keys)
{
if (keys.empty()) {
return;
}
const auto &groups = KeyCache::instance()->groups();
QSet<QString> foundGroups;
for (const auto &key : keys) {
for (const auto &group : groups) {
if (group.keys().contains(key)) {
if (ui.groupsList->findItems(group.name(), Qt::MatchExactly).isEmpty()) {
ui.groupsList->addItem(group.name());
}
}
}
}
ui.groupsLB.setVisible(ui.groupsList->count() > 0);
ui.groupsList->setVisible(ui.groupsList->count() > 0);
ui.groupsList->setVisible(ui.groupsList->count() > 0);
if (ui.groupsList->count() == 1) {
ui.groupsLB.setText(i18np("The certificate is part of a group. Deleting it may prevent this recipient from decrypting messages to:",
"The certificates are part of a group. Deleting them may prevent these recipients from decrypting messages to:",
selectedKeys.size() + unselectedKeys.size()));
} else {
ui.groupsLB.setText(i18np("The certificate is part of several groups. Deleting it may prevent the recipient from decrypting messages to:",
"The certificates are part of several groups. Deleting them may prevent the recipients from decrypting messages to:",
selectedKeys.size() + unselectedKeys.size()));
}
q->resize({maxRecommendedWidth({ui.selectedList, ui.unselectedList, ui.groupsList}) + 50, q->minimumSizeHint().height()});
}
private:
std::vector<Key> selectedKeys;
std::vector<Key> unselectedKeys;
struct UI {
QLabel selectedLB;
QListWidget *selectedList;
QLabel unselectedLB;
QListWidget *unselectedList;
QLabel groupsLB;
QListWidget *groupsList;
QDialogButtonBox buttonBox;
QVBoxLayout vlay;
explicit UI(DeleteCertificatesDialog *qq)
: selectedLB({}, qq)
, selectedList(new QListWidget)
, unselectedLB({}, qq)
, unselectedList(new QListWidget)
, groupsLB({}, qq)
, groupsList(new QListWidget)
, buttonBox(QDialogButtonBox::Ok | QDialogButtonBox::Cancel)
, vlay(qq)
{
Q_SET_OBJECT_NAME(selectedLB);
Q_SET_OBJECT_NAME(selectedList);
Q_SET_OBJECT_NAME(unselectedLB);
Q_SET_OBJECT_NAME(unselectedList);
Q_SET_OBJECT_NAME(groupsLB);
Q_SET_OBJECT_NAME(groupsList);
Q_SET_OBJECT_NAME(buttonBox);
Q_SET_OBJECT_NAME(vlay);
+ selectedList->setAccessibleName(i18nc("@label", "Certificates"));
+ unselectedList->setAccessibleName(i18nc("@label", "Certificates"));
+ groupsList->setAccessibleName(i18nc("@label", "Groups"));
+
vlay.addWidget(&selectedLB);
vlay.addWidget(selectedList, 1);
vlay.addWidget(&unselectedLB);
vlay.addWidget(unselectedList, 1);
vlay.addWidget(&groupsLB);
vlay.addWidget(groupsList, 1);
vlay.addWidget(&buttonBox);
unselectedLB.setContextMenuPolicy(Qt::NoContextMenu);
buttonBox.button(QDialogButtonBox::Ok)->setText(i18nc("@action:button", "Delete"));
connect(&unselectedLB, &QLabel::linkActivated, qq, [qq]() {
KMessageBox::information(qq,
xi18nc("@info",
"<para>When you delete CA certificates (both root CAs and intermediate CAs), "
"the certificates issued by them will also be deleted.</para>"
"<para>This can be nicely seen in <application>Kleopatra</application>'s "
"hierarchical view mode: In this mode, if you delete a certificate that has "
"children, those children will also be deleted. Think of CA certificates as "
"folders containing other certificates: When you delete the folder, you "
"delete its contents, too.</para>"));
});
groupsLB.setVisible(false);
unselectedList->setVisible(false);
groupsList->setVisible(false);
connect(&buttonBox, SIGNAL(accepted()), qq, SLOT(accept()));
connect(&buttonBox, &QDialogButtonBox::rejected, qq, &QDialog::reject);
}
} ui;
};
DeleteCertificatesDialog::DeleteCertificatesDialog(QWidget *p)
: QDialog(p)
, d(new Private(this))
{
d->readConfig();
}
DeleteCertificatesDialog::~DeleteCertificatesDialog()
{
d->writeConfig();
}
void DeleteCertificatesDialog::setSelectedKeys(const std::vector<Key> &keys)
{
d->selectedKeys = keys;
for (const auto &key : keys) {
d->ui.selectedList->addItem(Formatting::summaryLine(key));
}
d->ui.selectedLB.setText(
i18np("The following certificate was selected for deletion:", "The following certificates were selected for deletion:", keys.size()));
d->checkGroups(keys);
}
void DeleteCertificatesDialog::setUnselectedKeys(const std::vector<Key> &keys)
{
d->unselectedKeys = keys;
d->ui.unselectedLB.setVisible(!keys.empty());
for (const auto &key : keys) {
d->ui.unselectedList->addItem(Formatting::summaryLine(key));
}
d->ui.unselectedList->setVisible(d->ui.unselectedList->count() > 0);
d->ui.unselectedLB.setText(
i18np("The following certificate will be deleted even though you did <b>not</b> "
"explicitly select it (<a href=\"whatsthis://\">Why?</a>):",
"The following certificates will be deleted even though you did <b>not</b> "
"explicitly select them (<a href=\"whatsthis://\">Why?</a>):",
keys.size()));
d->checkGroups(keys);
}
std::vector<Key> DeleteCertificatesDialog::keys() const
{
const std::vector<Key> sel = d->selectedKeys;
const std::vector<Key> uns = d->unselectedKeys;
std::vector<Key> result;
result.reserve(sel.size() + uns.size());
result.insert(result.end(), sel.begin(), sel.end());
result.insert(result.end(), uns.begin(), uns.end());
return result;
}
void DeleteCertificatesDialog::accept()
{
const std::vector<Key> sel = d->selectedKeys;
const std::vector<Key> uns = d->unselectedKeys;
const uint secret =
std::count_if(sel.cbegin(), sel.cend(), std::mem_fn(&Key::hasSecret)) + std::count_if(uns.cbegin(), uns.cend(), std::mem_fn(&Key::hasSecret));
const uint total = sel.size() + uns.size();
int ret = KMessageBox::Continue;
if (secret)
ret = KMessageBox::warningContinueCancel(this,
secret == total ? i18np("The certificate to be deleted is your own. "
"It contains private key material, "
"which is needed to decrypt past communication "
"encrypted to the certificate, and should therefore "
"not be deleted.",
"All of the certificates to be deleted "
"are your own. "
"They contain private key material, "
"which is needed to decrypt past communication "
"encrypted to the certificate, and should therefore "
"not be deleted.",
secret)
: i18np("One of the certificates to be deleted "
"is your own. "
"It contains private key material, "
"which is needed to decrypt past communication "
"encrypted to the certificate, and should therefore "
"not be deleted.",
"Some of the certificates to be deleted "
"are your own. "
"They contain private key material, "
"which is needed to decrypt past communication "
"encrypted to the certificate, and should therefore "
"not be deleted.",
secret),
i18nc("@title:window", "Secret Key Deletion"),
KStandardGuiItem::guiItem(KStandardGuiItem::Delete),
KStandardGuiItem::cancel(),
QString(),
KMessageBox::Notify | KMessageBox::Dangerous);
if (ret == KMessageBox::Continue) {
QDialog::accept();
} else {
QDialog::reject();
}
}
#include "moc_deletecertificatesdialog.cpp"
diff --git a/src/dialogs/lookupcertificatesdialog.cpp b/src/dialogs/lookupcertificatesdialog.cpp
index 57f1ed25a..83879effe 100644
--- a/src/dialogs/lookupcertificatesdialog.cpp
+++ b/src/dialogs/lookupcertificatesdialog.cpp
@@ -1,521 +1,522 @@
/*
This file is part of Kleopatra, the KDE keymanager
SPDX-FileCopyrightText: 2008 Klarälvdalens Datakonsult AB
SPDX-License-Identifier: GPL-2.0-or-later
*/
#include <config-kleopatra.h>
#include "lookupcertificatesdialog.h"
#include <view/textoverlay.h>
#include <kleopatra_debug.h>
#include <Libkleo/Formatting>
#include <Libkleo/GnuPG>
#include <Libkleo/KeyList>
#include <Libkleo/TreeWidget>
#include <KConfigGroup>
#include <KLocalizedString>
#include <KSeparator>
#include <KSharedConfig>
#include <KStandardActions>
#include <QClipboard>
#include <QDialogButtonBox>
#include <QGridLayout>
#include <QGuiApplication>
#include <QHeaderView>
#include <QLabel>
#include <QLineEdit>
#include <QMenu>
#include <QPushButton>
#include <QRegularExpression>
#include <QRegularExpressionValidator>
#include <QTreeView>
#include <QVBoxLayout>
#include <gpgme++/key.h>
using namespace Kleo;
using namespace Kleo::Dialogs;
using namespace GpgME;
static const int KeyWithOriginRole = 0x201;
class LookupCertificatesDialog::Private
{
friend class ::Kleo::Dialogs::LookupCertificatesDialog;
LookupCertificatesDialog *const q;
public:
enum Columns {
Name,
Email,
Fingerprint,
ValidFrom,
ValidUntil,
Protocol,
KeyID,
Origin,
};
explicit Private(LookupCertificatesDialog *qq);
~Private();
private:
void slotSelectionChanged()
{
enableDisableWidgets();
}
void slotSearchTextChanged()
{
enableDisableWidgets();
}
void slotSearchClicked()
{
Q_EMIT q->searchTextChanged(searchText());
}
void slotDetailsClicked()
{
if (!q->selectedCertificates().empty()) {
Q_EMIT q->detailsRequested(q->selectedCertificates().front().key);
}
}
void slotSaveAsClicked()
{
std::vector<GpgME::Key> keys;
for (const auto &[key, origin] : q->selectedCertificates()) {
keys.push_back(key);
}
Q_EMIT q->saveAsRequested(keys);
}
void readConfig();
void writeConfig();
void enableDisableWidgets();
QString searchText() const
{
return ui.findED->text().trimmed();
}
std::vector<KeyWithOrigin> selectedCertificates() const
{
const QAbstractItemView *const view = ui.resultTV;
if (!view) {
return {};
}
const auto sm = view->selectionModel();
Q_ASSERT(sm);
std::vector<KeyWithOrigin> keys;
for (const auto &index : sm->selectedRows()) {
const auto key = ui.resultTV->itemFromIndex(index)->data(Private::Name, KeyWithOriginRole).value<KeyWithOrigin>();
Q_ASSERT(!key.key.isNull());
keys.push_back(key);
}
return keys;
}
int numSelectedCertificates() const
{
return ui.resultTV->selectedItems().size();
}
void copySelectedValue()
{
auto clipboardData = ui.resultTV->currentIndex().data(Kleo::ClipboardRole).toString();
if (clipboardData.isEmpty()) {
clipboardData = ui.resultTV->currentIndex().data().toString();
}
QGuiApplication::clipboard()->setText(clipboardData);
}
QValidator *queryValidator();
void updateQueryMode();
private:
QueryMode queryMode = AnyQuery;
bool passive;
QValidator *anyQueryValidator = nullptr;
QValidator *emailQueryValidator = nullptr;
bool initial = false;
struct Ui {
QLabel *guidanceLabel;
QLabel *findLB;
QLineEdit *findED;
QPushButton *findPB;
Kleo::TreeWidget *resultTV;
TextOverlay *overlay;
QPushButton *selectAllPB;
QPushButton *deselectAllPB;
QPushButton *detailsPB;
QPushButton *saveAsPB;
QDialogButtonBox *buttonBox;
void setupUi(LookupCertificatesDialog *dialog)
{
auto verticalLayout = new QVBoxLayout{dialog};
auto gridLayout = new QGridLayout{};
int row = 0;
guidanceLabel = new QLabel{dialog};
gridLayout->addWidget(guidanceLabel, row, 0, 1, 3);
row++;
findLB = new QLabel{i18n("Find:"), dialog};
gridLayout->addWidget(findLB, row, 0, 1, 1);
findED = new QLineEdit{dialog};
findLB->setBuddy(findED);
gridLayout->addWidget(findED, row, 1, 1, 1);
findPB = new QPushButton{i18n("Search"), dialog};
findPB->setAutoDefault(false);
gridLayout->addWidget(findPB, row, 2, 1, 1);
row++;
gridLayout->addWidget(new KSeparator{Qt::Horizontal, dialog}, row, 0, 1, 3);
row++;
resultTV = new Kleo::TreeWidget(dialog);
+ resultTV->setAccessibleName(i18nc("@label", "Results"));
resultTV->setEnabled(true);
resultTV->setMinimumSize(QSize(400, 0));
overlay = new TextOverlay{resultTV, dialog};
overlay->hide();
gridLayout->addWidget(resultTV, row, 0, 1, 2);
auto buttonLayout = new QVBoxLayout{};
selectAllPB = new QPushButton{i18n("Select All"), dialog};
selectAllPB->setEnabled(false);
selectAllPB->setAutoDefault(false);
buttonLayout->addWidget(selectAllPB);
deselectAllPB = new QPushButton{i18n("Deselect All"), dialog};
deselectAllPB->setEnabled(false);
deselectAllPB->setAutoDefault(false);
buttonLayout->addWidget(deselectAllPB);
buttonLayout->addStretch();
detailsPB = new QPushButton{i18n("Details..."), dialog};
detailsPB->setEnabled(false);
detailsPB->setAutoDefault(false);
buttonLayout->addWidget(detailsPB);
saveAsPB = new QPushButton{i18n("Save As..."), dialog};
saveAsPB->setEnabled(false);
saveAsPB->setAutoDefault(false);
buttonLayout->addWidget(saveAsPB);
gridLayout->addLayout(buttonLayout, row, 2, 1, 1);
verticalLayout->addLayout(gridLayout);
buttonBox = new QDialogButtonBox{dialog};
buttonBox->setStandardButtons(QDialogButtonBox::Close | QDialogButtonBox::Save);
verticalLayout->addWidget(buttonBox);
QObject::connect(findED, SIGNAL(returnPressed()), findPB, SLOT(animateClick()));
QObject::connect(buttonBox, SIGNAL(accepted()), dialog, SLOT(accept()));
QObject::connect(buttonBox, SIGNAL(rejected()), dialog, SLOT(reject()));
QObject::connect(findPB, SIGNAL(clicked()), dialog, SLOT(slotSearchClicked()));
QObject::connect(detailsPB, SIGNAL(clicked()), dialog, SLOT(slotDetailsClicked()));
QObject::connect(saveAsPB, SIGNAL(clicked()), dialog, SLOT(slotSaveAsClicked()));
QObject::connect(findED, SIGNAL(textChanged(QString)), dialog, SLOT(slotSearchTextChanged()));
QMetaObject::connectSlotsByName(dialog);
resultTV->setHeaderLabels({
i18nc("@title:column", "Name"),
i18nc("@title:column", "Email"),
i18nc("@title:column", "Fingerprint"),
i18nc("@title:column", "Valid From"),
i18nc("@title:column", "Valid Until"),
i18nc("@title:column", "Protocol"),
i18nc("@title:column", "Key ID"),
i18nc("@title:column", "Origin"),
});
resultTV->setSelectionMode(QAbstractItemView::ExtendedSelection);
resultTV->setContextMenuPolicy(Qt::CustomContextMenu);
connect(resultTV, &QTreeView::customContextMenuRequested, dialog, [this, dialog](const auto &pos) {
auto menu = new QMenu;
menu->setAttribute(Qt::WA_DeleteOnClose, true);
menu->addAction(KStandardActions::copy(
dialog,
[dialog]() {
dialog->d->copySelectedValue();
},
dialog));
menu->popup(resultTV->mapToGlobal(pos));
});
}
explicit Ui(LookupCertificatesDialog *q)
{
q->setWindowTitle(i18nc("@title:window", "Lookup on Server"));
setupUi(q);
saveAsPB->hide(); // ### not yet implemented in LookupCertificatesCommand
findED->setClearButtonEnabled(true);
importPB()->setText(i18n("Import"));
importPB()->setEnabled(false);
connect(resultTV, SIGNAL(doubleClicked(QModelIndex)), q, SLOT(slotDetailsClicked()));
findED->setFocus();
connect(selectAllPB, &QPushButton::clicked, resultTV, &QTreeView::selectAll);
connect(deselectAllPB, &QPushButton::clicked, resultTV, &QTreeView::clearSelection);
}
QPushButton *importPB() const
{
return buttonBox->button(QDialogButtonBox::Save);
}
QPushButton *closePB() const
{
return buttonBox->button(QDialogButtonBox::Close);
}
} ui;
};
LookupCertificatesDialog::Private::Private(LookupCertificatesDialog *qq)
: q(qq)
, passive(false)
, ui(q)
{
connect(ui.resultTV->selectionModel(), SIGNAL(selectionChanged(QItemSelection, QItemSelection)), q, SLOT(slotSelectionChanged()));
updateQueryMode();
}
LookupCertificatesDialog::Private::~Private()
{
}
void LookupCertificatesDialog::Private::readConfig()
{
KConfigGroup configGroup(KSharedConfig::openStateConfig(), QStringLiteral("LookupCertificatesDialog"));
if (!ui.resultTV->restoreColumnLayout(QStringLiteral("LookupCertificatesDialog"))) {
ui.resultTV->setColumnHidden(Private::KeyID, true);
initial = true;
ui.resultTV->resizeToContentsLimited();
}
const QSize size = configGroup.readEntry("Size", QSize(600, 400));
if (size.isValid()) {
q->resize(size);
}
}
void LookupCertificatesDialog::Private::writeConfig()
{
KConfigGroup configGroup(KSharedConfig::openStateConfig(), QStringLiteral("LookupCertificatesDialog"));
configGroup.writeEntry("Size", q->size());
configGroup.sync();
}
static QString guidanceText(LookupCertificatesDialog::QueryMode mode)
{
switch (mode) {
default:
qCWarning(KLEOPATRA_LOG) << __func__ << "Unknown query mode:" << mode;
[[fallthrough]];
case LookupCertificatesDialog::AnyQuery:
return xi18nc("@info", "Enter a search term to search for matching certificates.");
case LookupCertificatesDialog::EmailQuery:
return xi18nc("@info", "Enter an email address to search for matching certificates.");
};
}
QValidator *LookupCertificatesDialog::Private::queryValidator()
{
switch (queryMode) {
default:
qCWarning(KLEOPATRA_LOG) << __func__ << "Unknown query mode:" << queryMode;
[[fallthrough]];
case AnyQuery: {
if (!anyQueryValidator) {
// allow any query with at least one non-whitespace character
anyQueryValidator = new QRegularExpressionValidator{QRegularExpression{QStringLiteral(".*\\S+.*")}, q};
}
return anyQueryValidator;
}
case EmailQuery: {
if (!emailQueryValidator) {
// allow anything that looks remotely like an email address, i.e.
// anything with an '@' surrounded by non-whitespace characters
const QRegularExpression simpleEmailRegex{QStringLiteral(".*\\S+@\\S+.*")};
emailQueryValidator = new QRegularExpressionValidator{simpleEmailRegex, q};
}
return emailQueryValidator;
}
}
}
void LookupCertificatesDialog::Private::updateQueryMode()
{
ui.guidanceLabel->setText(guidanceText(queryMode));
ui.findED->setValidator(queryValidator());
}
LookupCertificatesDialog::LookupCertificatesDialog(QWidget *p, Qt::WindowFlags f)
: QDialog(p, f)
, d(new Private(this))
{
d->ui.findPB->setEnabled(false);
d->readConfig();
}
LookupCertificatesDialog::~LookupCertificatesDialog()
{
d->writeConfig();
}
void LookupCertificatesDialog::setQueryMode(QueryMode mode)
{
d->queryMode = mode;
d->updateQueryMode();
}
LookupCertificatesDialog::QueryMode LookupCertificatesDialog::queryMode() const
{
return d->queryMode;
}
void LookupCertificatesDialog::setCertificates(const std::vector<KeyWithOrigin> &certs)
{
d->ui.resultTV->setFocus();
d->ui.resultTV->clear();
for (const auto &[cert, origin] : certs) {
auto item = new QTreeWidgetItem;
item->setData(Private::Name, Qt::DisplayRole, Formatting::prettyName(cert));
item->setData(Private::Email, Qt::DisplayRole, Formatting::prettyEMail(cert));
item->setData(Private::Fingerprint, Qt::DisplayRole, Formatting::prettyID(cert.primaryFingerprint()));
item->setData(Private::Fingerprint, Qt::AccessibleTextRole, Formatting::accessibleHexID(cert.primaryFingerprint()));
item->setData(Private::Fingerprint, Kleo::ClipboardRole, QString::fromLatin1(cert.primaryFingerprint()));
item->setData(Private::ValidFrom, Qt::DisplayRole, Formatting::creationDateString(cert));
item->setData(Private::ValidFrom, Qt::AccessibleTextRole, Formatting::accessibleCreationDate(cert));
item->setData(Private::ValidUntil, Qt::DisplayRole, Formatting::expirationDateString(cert));
item->setData(Private::ValidUntil, Qt::AccessibleTextRole, Formatting::accessibleExpirationDate(cert));
item->setData(Private::KeyID, Qt::DisplayRole, Formatting::prettyID(cert.keyID()));
item->setData(Private::KeyID, Qt::AccessibleTextRole, Formatting::accessibleHexID(cert.keyID()));
item->setData(Private::KeyID, Kleo::ClipboardRole, QString::fromLatin1(cert.keyID()));
if (cert.protocol() == Protocol::CMS) {
item->setData(Private::Origin, Qt::DisplayRole, i18n("LDAP"));
} else if (origin == GpgME::Key::OriginKS) {
if (keyserver().startsWith(QStringLiteral("ldap:")) || keyserver().startsWith(QStringLiteral("ldaps:"))) {
item->setData(Private::Origin, Qt::DisplayRole, i18n("LDAP"));
} else {
item->setData(Private::Origin, Qt::DisplayRole, i18n("Keyserver"));
}
} else {
item->setData(Private::Origin, Qt::DisplayRole, Formatting::origin(origin));
}
item->setData(Private::Protocol, Qt::DisplayRole, Formatting::displayName(cert.protocol()));
item->setData(Private::Name, KeyWithOriginRole, QVariant::fromValue(KeyWithOrigin{cert, origin}));
d->ui.resultTV->addTopLevelItem(item);
}
if (certs.size() == 1) {
d->ui.resultTV->setCurrentIndex(d->ui.resultTV->model()->index(0, 0));
}
if (d->initial && d->ui.resultTV->model()->rowCount() > 0) {
d->initial = false;
d->ui.resultTV->resizeToContentsLimited();
}
}
std::vector<KeyWithOrigin> LookupCertificatesDialog::selectedCertificates() const
{
return d->selectedCertificates();
}
void LookupCertificatesDialog::setPassive(bool on)
{
if (d->passive == on) {
return;
}
d->passive = on;
d->enableDisableWidgets();
}
bool LookupCertificatesDialog::isPassive() const
{
return d->passive;
}
void LookupCertificatesDialog::setSearchText(const QString &text)
{
d->ui.findED->setText(text);
}
QString LookupCertificatesDialog::searchText() const
{
return d->ui.findED->text();
}
void LookupCertificatesDialog::setOverlayText(const QString &text)
{
if (text.isEmpty()) {
d->ui.overlay->hideOverlay();
} else {
d->ui.overlay->setText(text);
d->ui.overlay->showOverlay();
}
d->ui.selectAllPB->setEnabled(text.isEmpty());
d->ui.deselectAllPB->setEnabled(text.isEmpty());
}
QString LookupCertificatesDialog::overlayText() const
{
return d->ui.overlay->text();
}
void LookupCertificatesDialog::accept()
{
Q_ASSERT(!selectedCertificates().empty());
Q_EMIT importRequested(selectedCertificates());
QDialog::accept();
}
void LookupCertificatesDialog::Private::enableDisableWidgets()
{
// enable/disable everything except 'close', based on passive:
const QList<QObject *> list = q->children();
for (QObject *const o : list) {
if (QWidget *const w = qobject_cast<QWidget *>(o)) {
w->setDisabled(passive && w != ui.closePB() && w != ui.buttonBox);
}
}
if (passive) {
return;
}
q->setOverlayText({});
ui.findPB->setEnabled(ui.findED->hasAcceptableInput());
const int n = q->selectedCertificates().size();
ui.detailsPB->setEnabled(n == 1);
ui.saveAsPB->setEnabled(n == 1);
ui.importPB()->setEnabled(n != 0);
ui.importPB()->setDefault(false); // otherwise Import becomes default button if enabled and return triggers both a search and accept()
}
#include "moc_lookupcertificatesdialog.cpp"
diff --git a/src/dialogs/trustchainwidget.cpp b/src/dialogs/trustchainwidget.cpp
index 69a191561..2a4ae2b0d 100644
--- a/src/dialogs/trustchainwidget.cpp
+++ b/src/dialogs/trustchainwidget.cpp
@@ -1,96 +1,97 @@
/* SPDX-FileCopyrightText: 2016 Klarälvdalens Datakonsult AB
SPDX-License-Identifier: GPL-2.0-or-later
*/
#include "trustchainwidget.h"
#include "kleopatra_debug.h"
#include <KLocalizedString>
#include <QDialogButtonBox>
#include <QPushButton>
#include <QTreeWidget>
#include <QTreeWidgetItem>
#include <QVBoxLayout>
#include <gpgme++/key.h>
#include <Libkleo/Formatting>
#include <Libkleo/KeyCache>
class TrustChainWidget::Private
{
TrustChainWidget *const q;
public:
Private(TrustChainWidget *qq)
: q(qq)
, ui{qq}
{
}
GpgME::Key key;
struct UI {
QTreeWidget *treeWidget;
UI(QWidget *widget)
{
auto mainLayout = new QVBoxLayout{widget};
mainLayout->setContentsMargins({});
treeWidget = new QTreeWidget{widget};
+ treeWidget->setAccessibleName(i18nc("@label", "Certificate chain"));
// Breeze draws no frame for scroll areas that are the only widget in a layout...unless we force it
treeWidget->setProperty("_breeze_force_frame", true);
treeWidget->setHeaderHidden(true);
mainLayout->addWidget(treeWidget);
}
} ui;
};
TrustChainWidget::TrustChainWidget(QWidget *parent)
: QWidget(parent)
, d(new Private(this))
{
}
TrustChainWidget::~TrustChainWidget()
{
}
void TrustChainWidget::setKey(const GpgME::Key &key)
{
if (key.protocol() != GpgME::CMS) {
qCDebug(KLEOPATRA_LOG) << "Trust chain is only supported for CMS keys";
return;
}
d->key = key;
d->ui.treeWidget->clear();
const auto chain = Kleo::KeyCache::instance()->findIssuers(key, Kleo::KeyCache::RecursiveSearch | Kleo::KeyCache::IncludeSubject);
if (chain.empty()) {
return;
}
QTreeWidgetItem *last = nullptr;
if (!chain.back().isRoot()) {
last = new QTreeWidgetItem(d->ui.treeWidget);
last->setText(0, i18n("Issuer Certificate Not Found (%1)", Kleo::Formatting::prettyDN(chain.back().issuerName())));
const QBrush &fg = d->ui.treeWidget->palette().brush(QPalette::Disabled, QPalette::WindowText);
last->setForeground(0, fg);
}
for (auto it = chain.rbegin(), end = chain.rend(); it != end; ++it) {
last = last ? new QTreeWidgetItem(last) : new QTreeWidgetItem(d->ui.treeWidget);
last->setText(0, Kleo::Formatting::prettyDN(it->userID(0).id()));
}
d->ui.treeWidget->expandAll();
}
GpgME::Key TrustChainWidget::key() const
{
return d->key;
}
#include "moc_trustchainwidget.cpp"
File Metadata
Details
Attached
Mime Type
text/x-diff
Expires
Sat, Feb 7, 5:28 PM (7 h, 37 m)
Storage Engine
local-disk
Storage Format
Raw Data
Storage Handle
95/ac/aa7fff6438e1aaa6dadabfd54eed
Attached To
rKLEOPATRA Kleopatra
Event Timeline
Log In to Comment