diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 04542d9..0b9888b 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -1,228 +1,236 @@ # target_include_directories does not handle empty include paths include_directories(${GPGME_INCLUDES}) add_definitions(-DTRANSLATION_DOMAIN=\"libkleopatra\") #add_definitions( -DQT_NO_CAST_FROM_ASCII ) #add_definitions( -DQT_NO_CAST_TO_ASCII ) kde_enable_exceptions() add_definitions( -DGPGMEPP_ERR_SOURCE_DEFAULT=13 ) # 13 is GPG_ERR_SOURCE_KLEO, even if gpg-error's too old to know about add_subdirectory( pics ) if (BUILD_TESTING) add_subdirectory( tests ) endif() ########### next target ############### set(libkleo_core_SRCS kleo/checksumdefinition.cpp kleo/defaultkeyfilter.cpp kleo/defaultkeygenerationjob.cpp kleo/dn.cpp kleo/enum.cpp kleo/exception.cpp kleo/kconfigbasedkeyfilter.cpp kleo/keyfiltermanager.cpp kleo/keyresolver.cpp + kleo/remarks.cpp models/keycache.cpp models/keylistmodel.cpp models/keylistsortfilterproxymodel.cpp models/keyrearrangecolumnsproxymodel.cpp models/subkeylistmodel.cpp models/useridlistmodel.cpp utils/filesystemwatcher.cpp utils/formatting.cpp utils/classify.cpp utils/gnupg.cpp utils/hex.cpp smartcard/card.cpp smartcard/openpgpcard.cpp smartcard/netkeycard.cpp smartcard/cardmanager.cpp ) ecm_qt_declare_logging_category(libkleo_core_SRCS HEADER libkleo_debug.h IDENTIFIER LIBKLEO_LOG CATEGORY_NAME org.kde.pim.libkleo) set(libkleo_ui_common_SRCS ui/dnattributeorderconfigwidget.cpp ui/kdhorizontalline.cpp ui/filenamerequester.cpp ui/messagebox.cpp ui/cryptoconfigmodule.cpp ui/cryptoconfigdialog.cpp ui/directoryserviceswidget.cpp ui/progressbar.cpp ui/progressdialog.cpp ui/auditlogviewer.cpp ) ecm_qt_declare_logging_category(libkleo_ui_common_SRCS HEADER kleo_ui_debug.h IDENTIFIER KLEO_UI_LOG CATEGORY_NAME org.kde.pim.kleo_ui) set(libkleo_ui_SRCS # make this a separate lib. ui/keylistview.cpp + ui/keytreeview.cpp ui/keyselectiondialog.cpp ui/keyrequester.cpp ui/keyapprovaldialog.cpp ui/newkeyapprovaldialog.cpp ui/keyselectioncombo.cpp + ui/headerview.cpp + smartcard/netkeywidget.cpp + smartcard/pgpcardwidget.cpp + smartcard/smartcardwidget.cpp + smartcard/nullpinwidget.cpp + smartcard/gencardkeydialog.cpp ) ki18n_wrap_ui(libkleo_ui_common_SRCS ui/directoryserviceswidget.ui ) set(kleo_LIB_SRCS ${libkleo_core_SRCS} ${libkleo_ui_SRCS} ${libkleo_ui_common_SRCS}) set(kleo_LIB_LIBS PUBLIC QGpgme Gpgmepp PRIVATE Qt5::Widgets KF5::I18n KF5::Completion KF5::ConfigCore KF5::CoreAddons KF5::WidgetsAddons KF5::ItemModels KF5::Codecs) if (KF5PimTextEdit_FOUND) add_definitions(-DHAVE_PIMTEXTEDIT) set(kleo_LIB_LIBS ${kleo_LIB_LIBS} PRIVATE KF5::PimTextEdit) endif() add_library(KF5Libkleo ${kleo_LIB_SRCS}) generate_export_header(KF5Libkleo BASE_NAME kleo) add_library(KF5::Libkleo ALIAS KF5Libkleo) if(WIN32) target_link_libraries(KF5Libkleo ${kleo_LIB_LIBS} ${GPGME_VANILLA_LIBRARIES} ) else() target_link_libraries(KF5Libkleo ${kleo_LIB_LIBS} ) endif() set_target_properties(KF5Libkleo PROPERTIES VERSION ${LIBKLEO_VERSION_STRING} SOVERSION ${LIBKLEO_SOVERSION} EXPORT_NAME Libkleo ) install(TARGETS KF5Libkleo EXPORT KF5LibkleoTargets ${KF5_INSTALL_TARGETS_DEFAULT_ARGS} ${LIBRARY_NAMELINK} ) target_include_directories(KF5Libkleo PUBLIC "$") target_include_directories(KF5Libkleo INTERFACE "$") ecm_generate_headers(libkleo_CamelCase_HEADERS HEADER_NAMES ChecksumDefinition DefaultKeyFilter DefaultKeyGenerationJob Dn Enum Exception KConfigBasedKeyFilter KeyFilter KeyFilterManager KeyResolver OidMap Predicates Stl_Util REQUIRED_HEADERS libkleo_HEADERS PREFIX Libkleo RELATIVE kleo ) ecm_generate_headers(libkleo_CamelCase_models_HEADERS HEADER_NAMES KeyCache KeyListModel KeyListModelInterface KeyListSortFilterProxyModel KeyRearrangeColumnsProxyModel SubkeyListModel UserIDListModel REQUIRED_HEADERS libkleo_models_HEADERS PREFIX Libkleo RELATIVE models ) ecm_generate_headers(libkleo_CamelCase_utils_HEADERS HEADER_NAMES Classify FileSystemWatcher Formatting GnuPG REQUIRED_HEADERS libkleo_utils_HEADERS PREFIX Libkleo RELATIVE utils ) ecm_generate_headers(libkleo_CamelCase_smartcard_HEADERS HEADER_NAMES Card NetKeyCard OpenPGPCard CardManager REQUIRED_HEADERS libkleo_smartcard_HEADERS PREFIX Libkleo RELATIVE smartcard ) ecm_generate_headers(libkleo_CamelCase_ui_HEADERS HEADER_NAMES CryptoConfigDialog CryptoConfigModule DNAttributeOrderConfigWidget DirectoryServicesWidget FileNameRequester KDHorizontalLine KeyApprovalDialog NewKeyApprovalDialog KeyRequester KeySelectionCombo KeySelectionDialog MessageBox ProgressDialog REQUIRED_HEADERS libkleo_ui_HEADERS PREFIX Libkleo RELATIVE ui ) ecm_generate_pri_file(BASE_NAME Libkleo LIB_NAME KF5Libkleo DEPS "QGpgme" FILENAME_VAR PRI_FILENAME INCLUDE_INSTALL_DIR ${KDE_INSTALL_INCLUDEDIR_KF5}/Libkleo ) install(FILES ${libkleo_CamelCase_HEADERS} ${libkleo_CamelCase_models_HEADERS} ${libkleo_CamelCase_ui_HEADERS} ${libkleo_CamelCase_utils_HEADERS} ${libkleo_CamelCase_smartcard_HEADERS} DESTINATION ${KDE_INSTALL_INCLUDEDIR_KF5}/Libkleo COMPONENT Devel ) install(FILES ${CMAKE_CURRENT_BINARY_DIR}/kleo_export.h ${libkleo_HEADERS} ${libkleo_models_HEADERS} ${libkleo_ui_HEADERS} ${libkleo_utils_HEADERS} ${libkleo_smartcard_HEADERS} DESTINATION ${KDE_INSTALL_INCLUDEDIR_KF5}/libkleo COMPONENT Devel ) install(FILES ${PRI_FILENAME} DESTINATION ${ECM_MKSPECS_INSTALL_DIR}) if ( WIN32 ) install ( FILES libkleopatrarc-win32.desktop DESTINATION ${KDE_INSTALL_CONFDIR} RENAME libkleopatrarc ) else () install ( FILES libkleopatrarc.desktop DESTINATION ${KDE_INSTALL_CONFDIR} RENAME libkleopatrarc ) endif () diff --git a/src/kleo/remarks.cpp b/src/kleo/remarks.cpp new file mode 100644 index 0000000..f90ec40 --- /dev/null +++ b/src/kleo/remarks.cpp @@ -0,0 +1,89 @@ +/* utils/remarks.cpp + + This file is part of Kleopatra, the KDE keymanager + Copyright (c) 2019 by g10code GmbH + + Kleopatra is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + Kleopatra is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + + In addition, as a special exception, the copyright holders give + permission to link the code of this program with any edition of + the Qt library by Trolltech AS, Norway (or with modified versions + of Qt that use the same license as Qt), and distribute linked + combinations including the two. You must obey the GNU General + Public License in all respects for all of the code used other than + Qt. If you modify this file, you may extend this exception to + your version of the file, but you are not obligated to do so. If + you do not wish to do so, delete this exception statement from + your version. +*/ + +#include "remarks.h" +#include "libkleo_debug.h" + +#include +#include +#include + +using namespace Kleo; + +bool Remarks::remarksEnabled() +{ + const KConfigGroup conf(KSharedConfig::openConfig(), "RemarkSettings"); + return conf.readEntry("RemarksEnabled", false); +} + +void Remarks::enableRemarks(bool enable) +{ + KConfigGroup conf(KSharedConfig::openConfig(), "RemarkSettings"); + conf.writeEntry("RemarksEnabled", enable); + KeyCache::mutableInstance()->enableRemarks(enable); +} + +GpgME::Key Remarks::remarkKey() +{ + const KConfigGroup conf(KSharedConfig::openConfig(), "RemarkSettings"); + const auto remarkKeyFpr = conf.readEntry("RemarkKeyFpr", QString()); + GpgME::Key key; + if (remarkKeyFpr.isEmpty()) { + return key; + } + key = KeyCache::instance()->findByKeyIDOrFingerprint(remarkKeyFpr.toLatin1().constData()); + if (key.isNull()) { + qCDebug(LIBKLEO_LOG) << "Failed to find remark key: " << remarkKeyFpr; + return key; + } + return key; +} + +std::vector Remarks::remarkKeys() +{ + std::vector ret; + for (const auto &key: KeyCache::instance()->keys()) { + if (key.isNull() || key.isRevoked() || key.isExpired() || + key.isDisabled() || key.isInvalid() || key.protocol() != GpgME::OpenPGP) { + continue; + } + if (key.ownerTrust() >= GpgME::Key::Full) { + ret.push_back(key); + } + } + return ret; +} + +void Remarks::setRemarkKey(const GpgME::Key &key) +{ + KConfigGroup conf(KSharedConfig::openConfig(), "RemarkSettings"); + conf.writeEntry("RemarkKeyFpr", key.isNull() ? QString() : QString::fromLatin1(key.primaryFingerprint())); +} diff --git a/src/kleo/remarks.h b/src/kleo/remarks.h new file mode 100644 index 0000000..7751990 --- /dev/null +++ b/src/kleo/remarks.h @@ -0,0 +1,54 @@ +#ifndef REMARKS_H +#define REMARKS_H +/* utils/remarks.h + + This file is part of Kleopatra, the KDE keymanager + Copyright (c) 2019 by g10code GmbH + + Kleopatra is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + Kleopatra is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + + In addition, as a special exception, the copyright holders give + permission to link the code of this program with any edition of + the Qt library by Trolltech AS, Norway (or with modified versions + of Qt that use the same license as Qt), and distribute linked + combinations including the two. You must obey the GNU General + Public License in all respects for all of the code used other than + Qt. If you modify this file, you may extend this exception to + your version of the file, but you are not obligated to do so. If + you do not wish to do so, delete this exception statement from + your version. +*/ + +#include + +#include + +namespace Kleo +{ +namespace Remarks +{ +/* Helper functions to work with remark configuration */ +bool remarksEnabled(); +void enableRemarks(bool enable); +/* Read / write a single remark key into configuration. */ +GpgME::Key remarkKey(); +void setRemarkKey(const GpgME::Key &key); + +/* Get multiple keys to use for remarks. Currently + * this returns all fully trusted OpenPGP Keys. */ +std::vector remarkKeys(); +} +} // namespace Kleo +#endif // REMARKS_H diff --git a/src/smartcard/gencardkeydialog.cpp b/src/smartcard/gencardkeydialog.cpp new file mode 100644 index 0000000..b2ca40f --- /dev/null +++ b/src/smartcard/gencardkeydialog.cpp @@ -0,0 +1,168 @@ +/* dialogs/gencardkeydialog.cpp + + This file is part of Kleopatra, the KDE keymanager + Copyright (c) 2017 by Bundesamt für Sicherheit in der Informationstechnik + Software engineering by Intevation GmbH + + Kleopatra is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + Kleopatra is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + + In addition, as a special exception, the copyright holders give + permission to link the code of this program with any edition of + the Qt library by Trolltech AS, Norway (or with modified versions + of Qt that use the same license as Qt), and distribute linked + combinations including the two. You must obey the GNU General + Public License in all respects for all of the code used other than + Qt. If you modify this file, you may extend this exception to + your version of the file, but you are not obligated to do so. If + you do not wish to do so, delete this exception statement from + your version. +*/ + +#include "gencardkeydialog.h" + +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include + +using namespace Kleo; + +class GenCardKeyDialog::Private +{ +public: + Private(GenCardKeyDialog *qq): q(qq) + { + auto *vBox = new QVBoxLayout(q); + auto *grid = new QGridLayout; + vBox->addLayout(grid); + + auto bbox = new QDialogButtonBox(QDialogButtonBox::Ok | QDialogButtonBox::Cancel, qq); + + mOkButton = bbox->button(QDialogButtonBox::Ok); + + mOkButton->setDefault(true); + mOkButton->setShortcut(Qt::CTRL | Qt::Key_Return); + connect(bbox, &QDialogButtonBox::rejected, q, [this]() {q->reject();}); + connect(bbox, &QDialogButtonBox::accepted, q, [this]() {accept();}); + + vBox->addWidget(bbox); + + const KEMailSettings e; + mNameEdit = new QLineEdit(e.getSetting(KEMailSettings::RealName)); + mEmailEdit = new QLineEdit(e.getSetting(KEMailSettings::EmailAddress)); + + connect(mEmailEdit, &QLineEdit::textChanged, q, [this]() {checkAcceptable();}); + + auto nameLabel = new QLabel(i18n("Name:")); + auto mailLabel = new QLabel(i18n("EMail:")); + mInvalidEmailLabel = new QLabel(QStringLiteral("%1").arg( + i18n("Invalid EMail"))); + int row = 0; + grid->addWidget(nameLabel, row, 0); + grid->addWidget(mNameEdit, row++, 1); + grid->addWidget(mailLabel, row, 0); + grid->addWidget(mEmailEdit, row++, 1); + grid->addWidget(mInvalidEmailLabel, row++, 1); + + // In the future GnuPG may support more algos but for now + // (2.1.18) we are stuck with RSA for on card generation. + auto rsaLabel = new QLabel(i18n("RSA Keysize:")); + mKeySizeCombo = new QComboBox; + + grid->addWidget(rsaLabel, row, 0); + grid->addWidget(mKeySizeCombo, row++, 1); + + mBackupCheckBox = new QCheckBox(i18n("Backup encryption key")); + mBackupCheckBox->setToolTip(i18n("Backup the encryption key in a file.") + QStringLiteral("
") + + i18n("You will be asked for a passphrase to protect that file during key generation.")); + + mBackupCheckBox->setChecked(true); + + grid->addWidget(mBackupCheckBox, row++, 0, 1, 2); + + q->setMinimumWidth(400); + + checkAcceptable(); + } + + void accept() + { + params.name = mNameEdit->text(); + params.email = mEmailEdit->text(); + params.keysize = mKeySizeCombo->currentText().toInt(); + params.algo = GpgME::Subkey::AlgoRSA; + params.backup = mBackupCheckBox->isChecked(); + q->accept(); + } + + void setSupportedSizes(const std::vector &sizes) + { + mKeySizeCombo->clear(); + for (auto size: sizes) { + mKeySizeCombo->addItem(QString::number(size)); + } + mKeySizeCombo->setCurrentIndex(mKeySizeCombo->findText(QStringLiteral("2048"))); + } + + void checkAcceptable() + { + // We only require a valid mail address + const QString mail = mEmailEdit->text(); + if (!mail.isEmpty() && + KEmailAddress::isValidSimpleAddress(mail)) { + mOkButton->setEnabled(true); + mInvalidEmailLabel->hide(); + return; + } + if (!mail.isEmpty()) { + mInvalidEmailLabel->show(); + } else { + mInvalidEmailLabel->hide(); + } + mOkButton->setEnabled(false); + } + + GenCardKeyDialog *q; + KeyParams params; + QPushButton *mOkButton; + QLineEdit *mNameEdit; + QLineEdit *mEmailEdit; + QLabel *mInvalidEmailLabel; + QComboBox *mKeySizeCombo; + QCheckBox *mBackupCheckBox; +}; + +GenCardKeyDialog::GenCardKeyDialog(QWidget *parent) : QDialog(parent), + d(new Private(this)) +{ +} + +void GenCardKeyDialog::setSupportedSizes(const std::vector &sizes) +{ + d->setSupportedSizes(sizes); +} + +GenCardKeyDialog::KeyParams GenCardKeyDialog::getKeyParams() const +{ + return d->params; +} diff --git a/src/smartcard/gencardkeydialog.h b/src/smartcard/gencardkeydialog.h new file mode 100644 index 0000000..462bac4 --- /dev/null +++ b/src/smartcard/gencardkeydialog.h @@ -0,0 +1,71 @@ +/* dialogs/gencardkeydialog.h + + This file is part of Kleopatra, the KDE keymanager + Copyright (c) 2017 by Bundesamt für Sicherheit in der Informationstechnik + Software engineering by Intevation GmbH + + Kleopatra is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + Kleopatra is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + + In addition, as a special exception, the copyright holders give + permission to link the code of this program with any edition of + the Qt library by Trolltech AS, Norway (or with modified versions + of Qt that use the same license as Qt), and distribute linked + combinations including the two. You must obey the GNU General + Public License in all respects for all of the code used other than + Qt. If you modify this file, you may extend this exception to + your version of the file, but you are not obligated to do so. If + you do not wish to do so, delete this exception statement from + your version. +*/ +#ifndef DIALOGS_GENCARDKEYDIALOG_H +#define DIALOGS_GENCARDKEYDIALOG_H + +#include + +#include + +#include +#include + +namespace Kleo +{ +class GenCardKeyDialog: public QDialog +{ +Q_OBJECT + +public: + struct KeyParams + { + QString name; + QString email; + QString comment; + int keysize; + GpgME::Subkey::PubkeyAlgo algo; + bool backup; + }; + explicit GenCardKeyDialog(QWidget *parent = nullptr); + + KeyParams getKeyParams() const; + + void setSupportedSizes(const std::vector &sizes); + +private: + class Private; + std::shared_ptr d; +}; +} // namespace Kleo + + +#endif // DIALOGS_GENCARDKEYDIALOG_H diff --git a/src/smartcard/netkeywidget.cpp b/src/smartcard/netkeywidget.cpp new file mode 100644 index 0000000..a2a580d --- /dev/null +++ b/src/smartcard/netkeywidget.cpp @@ -0,0 +1,248 @@ +/* view/netkeywidget.cpp + + This file is part of Kleopatra, the KDE keymanager + Copyright (c) 2017 Intevation GmbH + + Kleopatra is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + Kleopatra is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + + In addition, as a special exception, the copyright holders give + permission to link the code of this program with any edition of + the Qt library by Trolltech AS, Norway (or with modified versions + of Qt that use the same license as Qt), and distribute linked + combinations including the two. You must obey the GNU General + Public License in all respects for all of the code used other than + Qt. If you modify this file, you may extend this exception to + your version of the file, but you are not obligated to do so. If + you do not wish to do so, delete this exception statement from + your version. +*/ +#include "netkeywidget.h" +#include "nullpinwidget.h" +#include "ui/keytreeview.h" + +#include "libkleo_debug.h" + +#include "smartcard/netkeycard.h" +#include "models/keylistmodel.h" + +#include +#include +#include +#include +#include +#include + +#include +#include + +using namespace Kleo; +using namespace Kleo::SmartCard; + +NetKeyWidget::NetKeyWidget() : + mSerialNumber(new QLabel), + mVersionLabel(new QLabel), + mLearnKeysLabel(new QLabel), + mErrorLabel(new QLabel), + mNullPinWidget(new NullPinWidget()), + mLearnKeysBtn(new QPushButton), + mChangeNKSPINBtn(new QPushButton), + mChangeSigGPINBtn(new QPushButton), + mTreeView(new KeyTreeView(this)), + mArea(new QScrollArea) +{ + auto vLay = new QVBoxLayout; + + // Set up the scroll are + mArea->setFrameShape(QFrame::NoFrame); + mArea->setWidgetResizable(true); + auto mAreaWidget = new QWidget; + mAreaWidget->setLayout(vLay); + mArea->setWidget(mAreaWidget); + auto scrollLay = new QVBoxLayout(this); + scrollLay->addWidget(mArea); + + // Add general widgets + mVersionLabel->setTextInteractionFlags(Qt::TextBrowserInteraction); + vLay->addWidget(mVersionLabel, 0, Qt::AlignLeft); + + mSerialNumber->setTextInteractionFlags(Qt::TextBrowserInteraction); + + auto hLay1 = new QHBoxLayout; + hLay1->addWidget(new QLabel(i18n("Serial number:"))); + hLay1->addWidget(mSerialNumber); + hLay1->addStretch(1); + vLay->addLayout(hLay1); + + vLay->addWidget(mNullPinWidget); + + + auto line1 = new QFrame(); + line1->setFrameShape(QFrame::HLine); + vLay->addWidget(line1); + vLay->addWidget(new QLabel(QStringLiteral("%1").arg(i18n("Certificates:"))), 0, Qt::AlignLeft); + + mLearnKeysLabel = new QLabel(i18n("There are unknown certificates on this card.")); + mLearnKeysBtn->setText(i18nc("@action", "Load Certificates")); + connect(mLearnKeysBtn, &QPushButton::clicked, this, [this] () { +#if 0 + mLearnKeysBtn->setEnabled(false); + auto cmd = new LearnCardKeysCommand(GpgME::CMS); + cmd->setParentWidget(this); + cmd->start(); + TODO libkleo port + auto icon = KleopatraApplication::instance()->sysTrayIcon(); + if (icon) { + icon->setLearningInProgress(true); + } + + connect(cmd, &Command::finished, this, [icon] () { + ReaderStatus::mutableInstance()->updateStatus(); + icon->setLearningInProgress(false); + }); +#endif + }); + + auto hLay2 = new QHBoxLayout; + hLay2->addWidget(mLearnKeysLabel); + hLay2->addWidget(mLearnKeysBtn); + hLay2->addStretch(1); + vLay->addLayout(hLay2); + + mErrorLabel->setVisible(false); + vLay->addWidget(mErrorLabel); + + // The certificate view + mTreeView->setHierarchicalModel(AbstractKeyListModel::createHierarchicalKeyListModel(mTreeView)); + mTreeView->setHierarchicalView(true); + +#if 0 + TODO libkleo port: + connect(mTreeView->view(), &QAbstractItemView::doubleClicked, this, [this] (const QModelIndex &idx) { + const auto klm = dynamic_cast (mTreeView->view()->model()); + if (!klm) { + qCDebug(LIBKLEO_LOG) << "Unhandled Model: " << mTreeView->view()->model()->metaObject()->className(); + return; + } + auto cmd = new DetailsCommand(klm->key(idx), nullptr); + cmd->setParentWidget(this); + cmd->start(); + }); + vLay->addWidget(mTreeView); +#endif + + // The action area + auto line2 = new QFrame(); + line2->setFrameShape(QFrame::HLine); + vLay->addWidget(line2); + vLay->addWidget(new QLabel(QStringLiteral("%1").arg(i18n("Actions:"))), 0, Qt::AlignLeft); + + mChangeNKSPINBtn->setText(i18nc("NKS is an identifier for a type of keys on a NetKey card", "Change NKS PIN")); + mChangeSigGPINBtn->setText(i18nc("SigG is an identifier for a type of keys on a NetKey card", "Change SigG PIN")); + + connect(mChangeNKSPINBtn, &QPushButton::clicked, this, [this] () { + mChangeNKSPINBtn->setEnabled(false); + doChangePin(false); + }); + connect(mChangeSigGPINBtn, &QPushButton::clicked, this, [this] () { + mChangeSigGPINBtn->setEnabled(false); + doChangePin(true); + }); + + auto hLay3 = new QHBoxLayout(); + hLay3->addWidget(mChangeNKSPINBtn); + hLay3->addWidget(mChangeSigGPINBtn); + hLay3->addStretch(1); + + vLay->addLayout(hLay3); + vLay->addStretch(1); +} + +void NetKeyWidget::setCard(const NetKeyCard* card) +{ + mVersionLabel->setText(i18nc("1 is a Version number", "NetKey v%1 Card", card->appVersion())); + mSerialNumber->setText(QString::fromStdString(card->serialNumber())); + + /* According to users of NetKey Cards it is fairly uncommon + * to use SigG Certificates at all. So it should be optional to set the pins. */ + mNullPinWidget->setVisible(card->hasNKSNullPin() /*|| card->hasSigGNullPin()*/); + + mNullPinWidget->setSigGVisible(false/*card->hasSigGNullPin()*/); + mNullPinWidget->setNKSVisible(card->hasNKSNullPin()); + mChangeNKSPINBtn->setEnabled(!card->hasNKSNullPin()); + + if (card->hasSigGNullPin()) { + mChangeSigGPINBtn->setText(i18nc("SigG is an identifier for a type of keys on a NetKey card", + "Set SigG PIN")); + } else { + mChangeSigGPINBtn->setText(i18nc("SigG is an identifier for a type of keys on a NetKey card", + "Change SigG PIN")); + } + + mLearnKeysBtn->setEnabled(true); + mLearnKeysBtn->setVisible(card->canLearnKeys()); + mTreeView->setVisible(!card->canLearnKeys()); + mLearnKeysLabel->setVisible(card->canLearnKeys()); + + const auto errMsg = card->errorMsg(); + if (!errMsg.isEmpty()) { + mErrorLabel->setText(QStringLiteral("%1: %2").arg(i18n("Error")).arg(errMsg)); + mErrorLabel->setVisible(true); + } else { + mErrorLabel->setVisible(false); + } + + const auto keys = card->keys(); + mTreeView->setKeys(keys); +} + +void NetKeyWidget::handleResult(const GpgME::Error &err, QPushButton *btn) +{ + btn->setEnabled(true); + if (err.isCanceled()) { + return; + } + if (err) { + KMessageBox::error(this, i18nc("@info", + "Failed to set PIN: %1", QString::fromLatin1(err.asString())), + i18nc("@title", "Error")); + return; + } +} + +void NetKeyWidget::setSigGPinSettingResult(const GpgME::Error &err) +{ + handleResult(err, mChangeSigGPINBtn); +} + +void NetKeyWidget::setNksPinSettingResult(const GpgME::Error &err) +{ + handleResult(err, mChangeNKSPINBtn); +} + +void NetKeyWidget::doChangePin(bool sigG) +{ +#if 0 + TODO libkleo port: + if (sigG) { + ReaderStatus::mutableInstance() + ->startSimpleTransaction("SCD PASSWD PW1.CH.SIG", + this, "setSigGPinSettingResult"); + } else { + ReaderStatus::mutableInstance() + ->startSimpleTransaction("SCD PASSWD PW1.CH", + this, "setNksPinSettingResult"); + } +#endif +} diff --git a/src/smartcard/netkeywidget.h b/src/smartcard/netkeywidget.h new file mode 100644 index 0000000..a03f1b4 --- /dev/null +++ b/src/smartcard/netkeywidget.h @@ -0,0 +1,86 @@ +/* view/netkeywidget.h + + This file is part of Kleopatra, the KDE keymanager + Copyright (c) 2017 Intevation GmbH + 2020 g10 Code GmbH + + Kleopatra is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + Kleopatra is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + + In addition, as a special exception, the copyright holders give + permission to link the code of this program with any edition of + the Qt library by Trolltech AS, Norway (or with modified versions + of Qt that use the same license as Qt), and distribute linked + combinations including the two. You must obey the GNU General + Public License in all respects for all of the code used other than + Qt. If you modify this file, you may extend this exception to + your version of the file, but you are not obligated to do so. If + you do not wish to do so, delete this exception statement from + your version. +*/ +#ifndef VIEW_NETKEYWIDGET_H +#define VIEW_NETKEYWIDGET_H + +#include +#include + +#include + +#include "kleo_export.h" + +class QLabel; +class QPushButton; +class QScrollArea; + +namespace Kleo +{ +class NullPinWidget; +class KeyTreeView; + +namespace SmartCard +{ +class NetKeyCard; +} // namespace SmartCard + +class KLEO_EXPORT NetKeyWidget: public QWidget +{ + Q_OBJECT +public: + NetKeyWidget(); + + void setCard(const SmartCard::NetKeyCard* card); + +private: + void handleResult(const GpgME::Error &err, QPushButton *btn); + void doChangePin(bool sigG); + +private Q_SLOTS: + void setSigGPinSettingResult(const GpgME::Error &err); + void setNksPinSettingResult(const GpgME::Error &err); + +private: + QLabel *mSerialNumber, + *mVersionLabel, + *mLearnKeysLabel, + *mErrorLabel; + NullPinWidget *mNullPinWidget; + QPushButton *mLearnKeysBtn, + *mChangeNKSPINBtn, + *mChangeSigGPINBtn; + KeyTreeView *mTreeView; + QScrollArea *mArea; +}; +} // namespace Kleo + +#endif // VIEW_NETKEYWIDGET_H diff --git a/src/smartcard/nullpinwidget.cpp b/src/smartcard/nullpinwidget.cpp new file mode 100644 index 0000000..af7715f --- /dev/null +++ b/src/smartcard/nullpinwidget.cpp @@ -0,0 +1,151 @@ +/* view/nullpinwidget.cpp + + This file is part of Kleopatra, the KDE keymanager + Copyright (c) 2017 Intevation GmbH + + Kleopatra is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + Kleopatra is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + + In addition, as a special exception, the copyright holders give + permission to link the code of this program with any edition of + the Qt library by Trolltech AS, Norway (or with modified versions + of Qt that use the same license as Qt), and distribute linked + combinations including the two. You must obey the GNU General + Public License in all respects for all of the code used other than + Qt. If you modify this file, you may extend this exception to + your version of the file, but you are not obligated to do so. If + you do not wish to do so, delete this exception statement from + your version. +*/ + +#include "nullpinwidget.h" + +#include "libkleo_debug.h" + +#include + +#include +#include +#include +#include + +#include +#include + +#include "netkeycard.h" + +using namespace Kleo; +using namespace Kleo::SmartCard; + +NullPinWidget::NullPinWidget() +{ + const auto nullTitle = i18nc("NullPIN is a word that is used all over in the netkey " + "documentation and should be understandable by Netkey cardholders", + "The NullPIN is still active on this card."); + const auto nullDescription = i18n("You need to set a PIN before you can use the certificates."); + const auto descriptionLbl = new QLabel(QStringLiteral("%1
%2").arg(nullTitle).arg(nullDescription)); + + auto vLay = new QVBoxLayout(this); + vLay->addWidget(descriptionLbl, 0, Qt::AlignCenter); + + mNKSBtn = new QPushButton(i18nc("NKS is an identifier for a type of keys on a NetKey card", "Set NKS PIN")); + mSigGBtn = new QPushButton(i18nc("SigG is an identifier for a type of keys on a NetKey card", "Set SigG PIN")); + + connect(mNKSBtn, &QPushButton::clicked, this, [this] () { + mNKSBtn->setEnabled(false); + doChangePin(false); + }); + connect(mSigGBtn, &QPushButton::clicked, this, [this] () { + mSigGBtn->setEnabled(false); + doChangePin(true); + }); + + auto hLayBtn = new QHBoxLayout; + hLayBtn->addStretch(1); + hLayBtn->addWidget(mNKSBtn); + hLayBtn->addWidget(mSigGBtn); + hLayBtn->addStretch(1); + + vLay->addLayout(hLayBtn); +} + +void NullPinWidget::doChangePin(bool sigG) +{ + auto ret = KMessageBox::warningContinueCancel(this, + i18n("Setting a PIN is required but can't be reverted.") + + QStringLiteral("

%1

%2

").arg( + i18n("If you proceed you will be asked to enter a new PIN " + "and later to repeat that PIN.")).arg( + i18n("It will not be possible to recover the " + "card if the PIN has been entered wrongly more than 2 times.")), + i18n("Set initial PIN"), + KStandardGuiItem::cont(), + KStandardGuiItem::cancel()); + + if (ret != KMessageBox::Continue) { + return; + } +#if 0 + TODO libkleo-port + if (sigG) { + ReaderStatus::mutableInstance() + ->startSimpleTransaction("SCD PASSWD --nullpin PW1.CH.SIG", + this, "setSigGPinSettingResult"); + } else { + ReaderStatus::mutableInstance() + ->startSimpleTransaction("SCD PASSWD --nullpin PW1.CH", + this, "setNksPinSettingResult"); + } +#endif +} + +void NullPinWidget::handleResult(const GpgME::Error &err, QPushButton *btn) +{ + btn->setEnabled(true); + if (err.isCanceled()) { + return; + } + if (err) { + KMessageBox::error(this, i18nc("@info", + "Failed to set PIN: %1", QString::fromLatin1(err.asString())), + i18nc("@title", "Error")); + return; + } + btn->setVisible(false); + + if (!mNKSBtn->isVisible() && !mSigGBtn->isVisible()) { + // Both pins are set, we can hide. + setVisible(false); + } +} + +void NullPinWidget::setSigGVisible(bool val) +{ + mSigGBtn->setVisible(val); +} + +void NullPinWidget::setNKSVisible(bool val) +{ + mNKSBtn->setVisible(val); +} + +void NullPinWidget::setSigGPinSettingResult(const GpgME::Error &err) +{ + handleResult(err, mSigGBtn); +} + +void NullPinWidget::setNksPinSettingResult(const GpgME::Error &err) +{ + handleResult(err, mNKSBtn); +} diff --git a/src/smartcard/nullpinwidget.h b/src/smartcard/nullpinwidget.h new file mode 100644 index 0000000..f934ce9 --- /dev/null +++ b/src/smartcard/nullpinwidget.h @@ -0,0 +1,72 @@ +#ifndef VIEW_NULLPINWIDGET_H +#define VIEW_NULLPINWIDGET_H +/* view/nullpinwidget.h + + This file is part of Kleopatra, the KDE keymanager + Copyright (c) 2017 Intevation GmbH + + Kleopatra is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + Kleopatra is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + + In addition, as a special exception, the copyright holders give + permission to link the code of this program with any edition of + the Qt library by Trolltech AS, Norway (or with modified versions + of Qt that use the same license as Qt), and distribute linked + combinations including the two. You must obey the GNU General + Public License in all respects for all of the code used other than + Qt. If you modify this file, you may extend this exception to + your version of the file, but you are not obligated to do so. If + you do not wish to do so, delete this exception statement from + your version. +*/ + +#include + +#include "kleo_export.h" + +class QPushButton; + +namespace GpgME +{ + class Error; +} // namespace GpgME + +namespace Kleo +{ + +class KLEO_EXPORT NullPinWidget: public QWidget +{ + Q_OBJECT +public: + NullPinWidget(); + + void setSigGVisible(bool val); + void setNKSVisible(bool val); + +private: + void doChangePin(bool sigG); + void handleResult(const GpgME::Error &err, QPushButton *btn); + +private Q_SLOTS: + void setSigGPinSettingResult(const GpgME::Error &err); + void setNksPinSettingResult(const GpgME::Error &err); + +private: + QPushButton *mNKSBtn, + *mSigGBtn; +}; + +} // namespace Kleo + +#endif // VIEW_NULLPINWIDGET_H diff --git a/src/smartcard/pgpcardwidget.cpp b/src/smartcard/pgpcardwidget.cpp new file mode 100644 index 0000000..1f1a33e --- /dev/null +++ b/src/smartcard/pgpcardwidget.cpp @@ -0,0 +1,517 @@ +/* smartcard/pgpcardwiget.cpp + + This file is part of Kleopatra, the KDE keymanager + Copyright (c) 2017 by Bundesamt für Sicherheit in der Informationstechnik + Software engineering by Intevation GmbH + Copyright (c) 2020 g10 Code GmbH + + Kleopatra is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + Kleopatra is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + + In addition, as a special exception, the copyright holders give + permission to link the code of this program with any edition of + the Qt library by Trolltech AS, Norway (or with modified versions + of Qt that use the same license as Qt), and distribute linked + combinations including the two. You must obey the GNU General + Public License in all respects for all of the code used other than + Qt. If you modify this file, you may extend this exception to + your version of the file, but you are not obligated to do so. If + you do not wish to do so, delete this exception statement from + your version. +*/ + +#include "pgpcardwidget.h" + +#include "libkleo_debug.h" + +#include "smartcard/openpgpcard.h" + +#include "gencardkeydialog.h" +#include "utils/gnupg.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +#include "models/keycache.h" +#include "utils/formatting.h" + +#include +#include +#include + +#include + +#if GPGMEPP_VERSION > 0x10801 // 1.8.1 +// TODO remove ifdef once > 1.8.1 is required +#include +# define GPGME_CAN_GENCARDKEY +#endif + +using namespace Kleo; +using namespace Kleo::SmartCard; + +namespace { +#ifdef GPGME_CAN_GENCARDKEY +class GenKeyThread: public QThread +{ + Q_OBJECT + + public: + explicit GenKeyThread(const GenCardKeyDialog::KeyParams ¶ms, const std::string &serial): + mSerial(serial), + mParams(params) + { + } + + GpgME::Error error() + { + return mErr; + } + + std::string bkpFile() + { + return mBkpFile; + } + protected: + void run() override { + GpgME::GpgGenCardKeyInteractor *ei = new GpgME::GpgGenCardKeyInteractor(mSerial); + ei->setKeySize(mParams.keysize); + ei->setNameUtf8(mParams.name.toStdString()); + ei->setEmailUtf8(mParams.email.toStdString()); + ei->setDoBackup(mParams.backup); + + const auto ctx = std::shared_ptr (GpgME::Context::createForProtocol(GpgME::OpenPGP)); + QGpgME::QByteArrayDataProvider dp; + GpgME::Data data(&dp); + + mErr = ctx->cardEdit(GpgME::Key(), std::unique_ptr (ei), data); + mBkpFile = ei->backupFileName(); + } + + private: + GpgME::Error mErr; + std::string mSerial; + GenCardKeyDialog::KeyParams mParams; + + std::string mBkpFile; +}; +#endif +} // Namespace + +PGPCardWidget::PGPCardWidget(): + mSerialNumber(new QLabel), + mCardHolderLabel(new QLabel), + mVersionLabel(new QLabel), + mSigningKey(new QLabel), + mEncryptionKey(new QLabel), + mAuthKey(new QLabel), + mUrlLabel(new QLabel), + mCardIsEmpty(false) +{ + auto grid = new QGridLayout; + int row = 0; + + // Set up the scroll are + auto area = new QScrollArea; + area->setFrameShape(QFrame::NoFrame); + area->setWidgetResizable(true); + auto areaWidget = new QWidget; + auto areaVLay = new QVBoxLayout(areaWidget); + areaVLay->addLayout(grid); + areaVLay->addStretch(1); + area->setWidget(areaWidget); + auto myLayout = new QVBoxLayout(this); + myLayout->addWidget(area); + + // Version and Serialnumber + grid->addWidget(mVersionLabel, row++, 0, 1, 2); + mVersionLabel->setTextInteractionFlags(Qt::TextBrowserInteraction); + grid->addWidget(new QLabel(i18n("Serial number:")), row, 0); + + grid->addWidget(mSerialNumber, row++, 1); + mSerialNumber->setTextInteractionFlags(Qt::TextBrowserInteraction); + + // Cardholder Row + grid->addWidget(new QLabel(i18nc("The owner of a smartcard. GnuPG refers to this as cardholder.", + "Cardholder:")), row, 0); + + grid->addWidget(mCardHolderLabel, row, 1); + mCardHolderLabel->setTextInteractionFlags(Qt::TextBrowserInteraction); + auto nameButtton = new QPushButton; + nameButtton->setIcon(QIcon::fromTheme(QStringLiteral("cell_edit"))); + nameButtton->setToolTip(i18n("Change")); + grid->addWidget(nameButtton, row++, 2); + connect(nameButtton, &QPushButton::clicked, this, &PGPCardWidget::changeNameRequested); + + // URL Row + grid->addWidget(new QLabel(i18nc("The URL under which a public key that " + "corresponds to a smartcard can be downloaded", + "Pubkey URL:")), row, 0); + grid->addWidget(mUrlLabel, row, 1); + + mUrlLabel->setTextInteractionFlags(Qt::TextBrowserInteraction); + auto urlButtton = new QPushButton; + urlButtton->setIcon(QIcon::fromTheme(QStringLiteral("cell_edit"))); + urlButtton->setToolTip(i18n("Change")); + grid->addWidget(urlButtton, row++, 2); + connect(urlButtton, &QPushButton::clicked, this, &PGPCardWidget::changeUrlRequested); + + // The keys + auto line1 = new QFrame(); + line1->setFrameShape(QFrame::HLine); + grid->addWidget(line1, row++, 0, 1, 4); + grid->addWidget(new QLabel(QStringLiteral("%1").arg(i18n("Keys:"))), row++, 0); + + grid->addWidget(new QLabel(i18n("Signature:")), row, 0); + grid->addWidget(mSigningKey, row++, 1); + mSigningKey->setTextInteractionFlags(Qt::TextBrowserInteraction); + + grid->addWidget(new QLabel(i18n("Encryption:")), row, 0); + grid->addWidget(mEncryptionKey, row++, 1); + mEncryptionKey->setTextInteractionFlags(Qt::TextBrowserInteraction); + + grid->addWidget(new QLabel(i18n("Authentication:")), row, 0); + grid->addWidget(mAuthKey, row++, 1); + mAuthKey->setTextInteractionFlags(Qt::TextBrowserInteraction); + + auto line2 = new QFrame(); + line2->setFrameShape(QFrame::HLine); + grid->addWidget(line2, row++, 0, 1, 4); + grid->addWidget(new QLabel(QStringLiteral("%1").arg(i18n("Actions:"))), row++, 0); + + auto actionLayout = new QHBoxLayout; + +#ifdef GPGME_CAN_GENCARDKEY + auto generateButton = new QPushButton(i18n("Generate new Keys")); + generateButton->setToolTip(i18n("Create a new primary key and generate subkeys on the card.")); + actionLayout->addWidget(generateButton); + connect(generateButton, &QPushButton::clicked, this, &PGPCardWidget::genkeyRequested); +#endif + + auto pinButtton = new QPushButton(i18n("Change PIN")); + pinButtton->setToolTip(i18n("Change the PIN required to unblock the smartcard.")); + actionLayout->addWidget(pinButtton); + connect(pinButtton, &QPushButton::clicked, this, [this] () {doChangePin(1);}); + + auto pukButton = new QPushButton(i18n("Change Admin PIN")); + pukButton->setToolTip(i18n("Change the PIN required to unlock the smartcard.")); + actionLayout->addWidget(pukButton); + connect(pukButton, &QPushButton::clicked, this, [this] () {doChangePin(3);}); + + auto resetCodeButton = new QPushButton(i18n("Change Reset Code")); + pukButton->setToolTip(i18n("Change the PIN required to reset the smartcard to an empty state.")); + actionLayout->addWidget(resetCodeButton); + connect(resetCodeButton, &QPushButton::clicked, this, [this] () {doChangePin(2);}); + + actionLayout->addStretch(-1); + grid->addLayout(actionLayout, row++, 0, 1, 4); + + grid->setColumnStretch(4, -1); +} + +void PGPCardWidget::setCard(const OpenPGPCard *card) +{ + const QString version = QString::fromStdString(card->cardVersion()); + + mIs21 = versionIsAtLeast("2.1", card->cardVersion().c_str()); + mVersionLabel->setText(i18nc("First placeholder is manufacturer, second placeholder is a version number", + "%1 OpenPGP v%2 card", QString::fromStdString(card->manufacturer()), + version)); + const QString sn = QString::fromStdString(card->serialNumber()).mid(16, 12); + mSerialNumber->setText(sn); + mRealSerial = card->serialNumber(); + + const auto holder = QString::fromStdString(card->cardHolder()); + const auto url = QString::fromStdString(card->pubkeyUrl()); + mCardHolderLabel->setText(holder.isEmpty() ? i18n("not set") : holder); + mUrl = url; + mUrlLabel->setText(url.isEmpty() ? i18n("not set") : + QStringLiteral("%1").arg(url.toHtmlEscaped())); + mUrlLabel->setOpenExternalLinks(true); + + updateKey(mSigningKey, card->sigFpr()); + updateKey(mEncryptionKey, card->encFpr()); + updateKey(mAuthKey, card->authFpr()); + mCardIsEmpty = card->authFpr().empty() && card->sigFpr().empty() && card->encFpr().empty(); +} + +void PGPCardWidget::doChangePin(int slot) +{ + qCWarning(LIBKLEO_LOG) << "Not implemented."; +} + +#ifdef GPGME_CAN_GENCARDKEY +void PGPCardWidget::doGenKey(GenCardKeyDialog *dlg) +{ + const auto params = dlg->getKeyParams(); + + auto progress = new QProgressDialog(this, Qt::CustomizeWindowHint | Qt::WindowTitleHint | Qt::Dialog); + progress->setAutoClose(true); + progress->setMinimumDuration(0); + progress->setMaximum(0); + progress->setMinimum(0); + progress->setModal(true); + progress->setCancelButton(nullptr); + progress->setWindowTitle(i18nc("@title:window", "Generating Keys")); + progress->setLabel(new QLabel(i18n("This may take several minutes..."))); + GenKeyThread *workerThread = new GenKeyThread(params, mRealSerial); + connect(workerThread, &QThread::finished, this, [this, workerThread, progress] { + progress->accept(); + progress->deleteLater(); + genKeyDone(workerThread->error(), workerThread->bkpFile()); + delete workerThread; + }); + workerThread->start(); + progress->exec(); +} + +void PGPCardWidget::genKeyDone(const GpgME::Error &err, const std::string &backup) +{ + if (err) { + KMessageBox::error(this, i18nc("@info", + "Failed to generate new key: %1", QString::fromLatin1(err.asString())), + i18nc("@title", "Error")); + return; + } + if (err.isCanceled()) { + return; + } + if (!backup.empty()) { + const auto bkpFile = QString::fromStdString(backup); + QFileInfo fi(bkpFile); + const auto target = QFileDialog::getSaveFileName(this, i18n("Save backup of encryption key"), + fi.fileName(), + QStringLiteral("%1 (*.gpg)").arg(i18n("Backup Key"))); + if (!target.isEmpty() && !QFile::copy(bkpFile, target)) { + KMessageBox::error(this, i18nc("@info", + "Failed to move backup. The backup key is still stored under: %1", bkpFile), + i18nc("@title", "Error")); + } else if (!target.isEmpty()) { + QFile::remove(bkpFile); + } + } + + KMessageBox::information(this, i18nc("@info", + "Successfully generated a new key for this card."), + i18nc("@title", "Success")); +} +#else +void PGPCardWidget::doGenKey(GenCardKeyDialog *) {} +void PGPCardWidget::genKeyDone(const GpgME::Error &, const std::string &) {} +#endif + +void PGPCardWidget::genkeyRequested() +{ + if (!mCardIsEmpty) { + auto ret = KMessageBox::warningContinueCancel(this, + i18n("The existing keys on this card will be deleted " + "and replaced by new keys.") + QStringLiteral("

") + + i18n("It will no longer be possible to decrypt past communication " + "encrypted for the existing key."), + i18n("Secret Key Deletion"), + KStandardGuiItem::guiItem(KStandardGuiItem::Delete), + KStandardGuiItem::cancel(), QString(), KMessageBox::Notify | KMessageBox::Dangerous); + + if (ret != KMessageBox::Continue) { + return; + } + } + + GenCardKeyDialog *dlg = new GenCardKeyDialog(this); + std::vector sizes; + sizes.push_back(1024); + sizes.push_back(2048); + sizes.push_back(3072); + // There is probably a better way to check for capabilities + if (mIs21) { + sizes.push_back(4096); + } + dlg->setSupportedSizes(sizes); + connect(dlg, &QDialog::accepted, this, [this, dlg] () { + doGenKey(dlg); + dlg->deleteLater(); + }); + dlg->setModal(true); + dlg->show(); +} + +void PGPCardWidget::changePinResult(const GpgME::Error &err) +{ + if (err) { + KMessageBox::error(this, i18nc("@info", + "PIN change failed: %1", QString::fromLatin1(err.asString())), + i18nc("@title", "Error")); + return; + } + if (!err.isCanceled()) { + KMessageBox::information(this, i18nc("@info", + "Code successfully changed."), + i18nc("@title", "Success")); + } +} + +void PGPCardWidget::changeNameRequested() +{ + QString text = mCardHolderLabel->text(); + while (true) { + bool ok = false; + text = QInputDialog::getText(this, i18n("Change cardholder"), + i18n("New name:"), QLineEdit::Normal, + text, &ok, Qt::WindowFlags(), + Qt::ImhLatinOnly); + if (!ok) { + return; + } + // Some additional restrictions imposed by gnupg + if (text.contains(QLatin1Char('<'))) { + KMessageBox::error(this, i18nc("@info", + "The \"<\" character may not be used."), + i18nc("@title", "Error")); + continue; + } + if (text.contains(QLatin1String(" "))) { + KMessageBox::error(this, i18nc("@info", + "Double spaces are not allowed"), + i18nc("@title", "Error")); + continue; + } + if (text.size() > 38) { + KMessageBox::error(this, i18nc("@info", + "The size of the name may not exceed 38 characters."), + i18nc("@title", "Error")); + } + break; + } + auto parts = text.split(QLatin1Char(' ')); + const auto lastName = parts.takeLast(); + const QString formatted = lastName + QStringLiteral("<<") + parts.join(QLatin1Char('<')); +#if 0 + TODO libkleo-port + ReaderStatus::mutableInstance() + ->startSimpleTransaction(QStringLiteral("SCD SETATTR DISP-NAME %1").arg(formatted).toUtf8().constData(), + this, "changeNameResult"); +#endif +} + +void PGPCardWidget::changeNameResult(const GpgME::Error &err) +{ + if (err) { + KMessageBox::error(this, i18nc("@info", + "Name change failed: %1", QString::fromLatin1(err.asString())), + i18nc("@title", "Error")); + return; + } + if (!err.isCanceled()) { + KMessageBox::information(this, i18nc("@info", + "Name successfully changed."), + i18nc("@title", "Success")); +#if 0 + TODO libkleo-port + ReaderStatus::mutableInstance()->updateStatus(); +#endif + } +} + +void PGPCardWidget::changeUrlRequested() +{ + QString text = mUrl; + while (true) { + bool ok = false; + text = QInputDialog::getText(this, i18n("Change the URL where the pubkey can be found"), + i18n("New pubkey URL:"), QLineEdit::Normal, + text, &ok, Qt::WindowFlags(), + Qt::ImhLatinOnly); + if (!ok) { + return; + } + // Some additional restrictions imposed by gnupg + if (text.size() > 254) { + KMessageBox::error(this, i18nc("@info", + "The size of the URL may not exceed 254 characters."), + i18nc("@title", "Error")); + } + break; + } +#if 0 + TODO libkleo-port + ReaderStatus::mutableInstance() + ->startSimpleTransaction(QStringLiteral("SCD SETATTR PUBKEY-URL %1").arg(text).toUtf8().constData(), + this, "changeUrlResult"); +#endif +} + +void PGPCardWidget::changeUrlResult(const GpgME::Error &err) +{ + if (err) { + KMessageBox::error(this, i18nc("@info", + "URL change failed: %1", QString::fromLatin1(err.asString())), + i18nc("@title", "Error")); + return; + } + if (!err.isCanceled()) { + KMessageBox::information(this, i18nc("@info", + "URL successfully changed."), + i18nc("@title", "Success")); +#if 0 + TODO libkleo-port + ReaderStatus::mutableInstance()->updateStatus(); +#endif + } +} + +void PGPCardWidget::updateKey(QLabel *label, const std::string &fpr) +{ + label->setText(QString::fromStdString(fpr)); + + if (fpr.empty()) { + label->setText(i18n("Slot empty")); + return; + } + + std::vector vec; + std::string keyid = fpr; + keyid.erase(0, keyid.size() - 16); + vec.push_back(keyid); + const auto subkeys = KeyCache::instance()->findSubkeysByKeyID(vec); + if (subkeys.empty() || subkeys[0].isNull()) { + label->setToolTip(i18n("Public key not found.")); + return; + } + QStringList toolTips; + for (const auto &sub: subkeys) { + // Yep you can have one subkey associated with multiple + // primary keys. + toolTips << Formatting::toolTip(sub.parent(), Formatting::Validity | + Formatting::StorageLocation | + Formatting::ExpiryDates | + Formatting::UserIDs | + Formatting::Fingerprint); + } + label->setToolTip(toolTips.join(QLatin1String("
"))); + return; +} + +#include "pgpcardwidget.moc" diff --git a/src/smartcard/pgpcardwidget.h b/src/smartcard/pgpcardwidget.h new file mode 100644 index 0000000..5d0c991 --- /dev/null +++ b/src/smartcard/pgpcardwidget.h @@ -0,0 +1,88 @@ +/* smartcard/pgpcardwiget.h + + This file is part of Kleopatra, the KDE keymanager + Copyright (c) 2017 by Bundesamt für Sicherheit in der Informationstechnik + Software engineering by Intevation GmbH + + Kleopatra is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + Kleopatra is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + + In addition, as a special exception, the copyright holders give + permission to link the code of this program with any edition of + the Qt library by Trolltech AS, Norway (or with modified versions + of Qt that use the same license as Qt), and distribute linked + combinations including the two. You must obey the GNU General + Public License in all respects for all of the code used other than + Qt. If you modify this file, you may extend this exception to + your version of the file, but you are not obligated to do so. If + you do not wish to do so, delete this exception statement from + your version. +*/ +#ifndef VIEW_PGPCARDWIDGET_H +#define VIEW_PGPCARDWIDGET_H + +#include +#include + +#include + +#include "kleo_export.h" + +class QLabel; + +namespace Kleo +{ +class GenCardKeyDialog; + +namespace SmartCard +{ +class OpenPGPCard; +} // namespace SmartCard + +class KLEO_EXPORT PGPCardWidget: public QWidget +{ + Q_OBJECT +public: + PGPCardWidget(); + + void setCard(const SmartCard::OpenPGPCard* card); + void doChangePin(int slot); + void doGenKey(GenCardKeyDialog *dlg); + void genKeyDone(const GpgME::Error &err, const std::string &backup); + +public Q_SLOTS: + void genkeyRequested(); + void changePinResult(const GpgME::Error &err); + void changeNameRequested(); + void changeNameResult(const GpgME::Error &err); + void changeUrlRequested(); + void changeUrlResult(const GpgME::Error &err); + +private: + void updateKey(QLabel *label, const std::string &fpr); + QLabel *mSerialNumber = nullptr, + *mCardHolderLabel = nullptr, + *mVersionLabel = nullptr, + *mSigningKey = nullptr, + *mEncryptionKey = nullptr, + *mAuthKey = nullptr, + *mUrlLabel = nullptr; + QString mUrl; + bool mCardIsEmpty = false; + bool mIs21 = false; + std::string mRealSerial; +}; +} // namespace Kleo + +#endif // VIEW_PGPCARDWIDGET_H diff --git a/src/smartcard/smartcardwidget.cpp b/src/smartcard/smartcardwidget.cpp new file mode 100644 index 0000000..3baeb67 --- /dev/null +++ b/src/smartcard/smartcardwidget.cpp @@ -0,0 +1,169 @@ +/* view/smartcardwidget.cpp + + This file is part of Kleopatra, the KDE keymanager + Copyright (c) 2017 by Bundesamt für Sicherheit in der Informationstechnik + Software engineering by Intevation GmbH + + Kleopatra is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + Kleopatra is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + + In addition, as a special exception, the copyright holders give + permission to link the code of this program with any edition of + the Qt library by Trolltech AS, Norway (or with modified versions + of Qt that use the same license as Qt), and distribute linked + combinations including the two. You must obey the GNU General + Public License in all respects for all of the code used other than + Qt. If you modify this file, you may extend this exception to + your version of the file, but you are not obligated to do so. If + you do not wish to do so, delete this exception statement from + your version. +*/ + +#include "smartcardwidget.h" +#include "openpgpcard.h" +#include "netkeycard.h" +#include "pgpcardwidget.h" +#include "netkeywidget.h" + +#include "cardmanager.h" + +#include "libkleo_debug.h" + +#include +#include +#include +#include +#include + +#include + +using namespace Kleo; +using namespace Kleo::SmartCard; + +namespace { +class PlaceHolderWidget: public QWidget +{ + Q_OBJECT +public: + PlaceHolderWidget() + { + auto lay = new QVBoxLayout; + lay->addStretch(-1); + + const QStringList supported = QStringList() << QStringLiteral("OpenPGP v2.0 - v3.3") + << QStringLiteral("Gnuk") + << QStringLiteral("NetKey v3"); + lay->addWidget(new QLabel(QStringLiteral("\t\t

") + + i18n("Please insert a compatible smartcard.") + QStringLiteral("

"))); + lay->addSpacing(10); + lay->addWidget(new QLabel(QStringLiteral("\t\t") + + i18n("Kleopatra currently supports the following card types:") + + QStringLiteral("
  • ") + supported.join(QLatin1String("
  • ")) + + QStringLiteral("
"))); + lay->addSpacing(10); + lay->addWidget(new QLabel(i18n("Refresh the view (F5) to update the smartcard status."))); + lay->addStretch(-1); + + auto hLay = new QHBoxLayout(this); + hLay->addStretch(-1); + hLay->addLayout(lay); + hLay->addStretch(-1); + lay->addStretch(-1); + } +}; +} // namespace + +class SmartCardWidget::Private +{ +public: + Private(SmartCardWidget *qq) : q(qq), mManager(CardManager::instance()) + { + QPushButton *backBtn = new QPushButton(QIcon::fromTheme(QStringLiteral("arrow-left")), i18n("Back")); + QHBoxLayout *backH = new QHBoxLayout; + backH->addWidget(backBtn); + backH->addWidget(new QLabel(QStringLiteral("

") + i18n("Smartcard Management") + + QStringLiteral("

"))); + backH->addStretch(-1); + + QVBoxLayout *vLay = new QVBoxLayout(q); + + + connect(backBtn, &QPushButton::clicked, q, [this] () {Q_EMIT (q->backRequested());}); + + vLay->addLayout(backH); + + mStack = new QStackedWidget; + vLay->addWidget(mStack); + + mPGPCardWidget = new PGPCardWidget; + mStack->addWidget(mPGPCardWidget); + + mNetKeyWidget = new NetKeyWidget; + mStack->addWidget(mNetKeyWidget); + + mPlaceHolderWidget = new PlaceHolderWidget; + mStack->addWidget(mPlaceHolderWidget); + + mStack->setCurrentWidget(mPlaceHolderWidget); + + connect (mManager, &CardManager::cardsMayHaveChanged, q, [this] () { + const auto cards = mManager->cards(); + if (!cards.size()) { + setCard(std::shared_ptr(new Card())); + } else { + // No support for multiple reader / cards currently + setCard(cards[0]); + } + }); + } + + void setCard(std::shared_ptr card) + { + if (card->appType() == Card::OpenPGPApplication) { + mPGPCardWidget->setCard(static_cast (card.get())); + mStack->setCurrentWidget(mPGPCardWidget); + } else if (card->appType() == Card::NksApplication) { + mNetKeyWidget->setCard(static_cast (card.get())); + mStack->setCurrentWidget(mNetKeyWidget); + } else { + mStack->setCurrentWidget(mPlaceHolderWidget); + } + } + + void reload() + { + mManager->startCardList(); + } + +private: + SmartCardWidget *q; + NetKeyWidget *mNetKeyWidget; + PGPCardWidget *mPGPCardWidget; + PlaceHolderWidget *mPlaceHolderWidget; + QStackedWidget *mStack; + CardManager *mManager; +}; + +SmartCardWidget::SmartCardWidget(QWidget *parent): + QWidget(parent), + d(new Private(this)) +{ +} + +void SmartCardWidget::reload() +{ + d->reload(); +} + +#include "smartcardwidget.moc" diff --git a/src/smartcard/smartcardwidget.h b/src/smartcard/smartcardwidget.h new file mode 100644 index 0000000..1c191f5 --- /dev/null +++ b/src/smartcard/smartcardwidget.h @@ -0,0 +1,59 @@ +#ifndef VIEW_SMARTCARDWIDGET_H +#define VIEW_SMARTCARDWIDGET_H +/* smartcard/smartcardwidget.h + + This file is part of Kleopatra, the KDE keymanager + Copyright (c) 2017 by Bundesamt für Sicherheit in der Informationstechnik + Software engineering by Intevation GmbH + + Kleopatra is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + Kleopatra is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + + In addition, as a special exception, the copyright holders give + permission to link the code of this program with any edition of + the Qt library by Trolltech AS, Norway (or with modified versions + of Qt that use the same license as Qt), and distribute linked + combinations including the two. You must obey the GNU General + Public License in all respects for all of the code used other than + Qt. If you modify this file, you may extend this exception to + your version of the file, but you are not obligated to do so. If + you do not wish to do so, delete this exception statement from + your version. +*/ + +#include +#include + +namespace Kleo +{ +/* SmartCardWidget a generic widget to interact with smartcards */ +class SmartCardWidget: public QWidget +{ + Q_OBJECT +public: + explicit SmartCardWidget(QWidget *parent = nullptr); + +public Q_SLOTS: + void reload(); + +Q_SIGNALS: + void backRequested(); + +private: + class Private; + std::shared_ptr d; +}; + +} // namespace Kleo +#endif // VIEW_SMARTCARDWIDGET_H diff --git a/src/ui/headerview.cpp b/src/ui/headerview.cpp new file mode 100644 index 0000000..f72af4f --- /dev/null +++ b/src/ui/headerview.cpp @@ -0,0 +1,242 @@ +/* -*- mode: c++; c-basic-offset:4 -*- + utils/headerview.cpp + + This file is part of Kleopatra, the KDE keymanager + Copyright (c) 2008 Klarälvdalens Datakonsult AB + + Kleopatra is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + Kleopatra is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + + In addition, as a special exception, the copyright holders give + permission to link the code of this program with any edition of + the Qt library by Trolltech AS, Norway (or with modified versions + of Qt that use the same license as Qt), and distribute linked + combinations including the two. You must obey the GNU General + Public License in all respects for all of the code used other than + Qt. If you modify this file, you may extend this exception to + your version of the file, but you are not obligated to do so. If + you do not wish to do so, delete this exception statement from + your version. +*/ + +#include "headerview.h" + +#include +#include + +using namespace Kleo; + +static std::vector section_sizes(const QHeaderView *view) +{ + Q_ASSERT(view); + std::vector result; + result.reserve(view->count()); + for (int i = 0, end = view->count(); i != end; ++i) { + result.push_back(view->sectionSize(i)); + } + return result; +} + +static void apply_section_sizes(QHeaderView *view, const std::vector &newSizes) +{ + Q_ASSERT(view); + for (unsigned int i = 0, end = newSizes.size(); i != end; ++i) { + view->resizeSection(i, newSizes[i]); + } +} + +namespace +{ + +template +inline typename T_container::value_type lookup(const T_container &c, unsigned int i, const typename T_container::value_type &defaultValue) +{ + return i < c.size() ? c[i] : defaultValue; +} + +} + +class HeaderView::Private +{ + friend class ::Kleo::HeaderView; + HeaderView *const q; +public: + Private(HeaderView *qq) + : q(qq), + mousePressed(false), + modes(), + sizes() + { + connect(q, SIGNAL(sectionCountChanged(int,int)), q, SLOT(_klhv_slotSectionCountChanged(int,int))); + connect(q, SIGNAL(sectionResized(int,int,int)), q, SLOT(_klhv_slotSectionResized(int,int,int))); + } + + void _klhv_slotSectionCountChanged(int oldCount, int newCount) + { + if (newCount == oldCount) { + return; + } + if (newCount < oldCount) { + return; + } + ensureNumSections(newCount); + for (unsigned int i = 0, end = std::min(newCount, modes.size()); i < end; ++i) { + q->QHeaderView::setSectionResizeMode(i, modes[i]); + } + apply_section_sizes(q, sizes); + } + + void _klhv_slotSectionResized(int idx, int, int newSize) + { + ensureNumSections(idx + 1); + sizes[idx] = newSize; + } + + void ensureNumSections(unsigned int num) + { + if (num > modes.size()) { + modes.resize(num, QHeaderView::Interactive); + } + if (num > sizes.size()) { + sizes.resize(num, q->defaultSectionSize()); + } + } + + bool mousePressed : 1; + std::vector modes; + std::vector sizes; +}; + +HeaderView::HeaderView(Qt::Orientation o, QWidget *p) + : QHeaderView(o, p), d(new Private(this)) +{ + +} + +HeaderView::~HeaderView() {} + +#if 0 +static std::vector calculate_section_sizes(const std::vector &oldSizes, int newLength, const std::vector &modes, int minSize) +{ + + if (oldSizes.empty()) { + hvDebug() << "no existing sizes"; + return std::vector(); + } + + int oldLength = 0, fixedLength = 0, stretchLength = 0; + int numStretchSections = 0; + for (unsigned int i = 0, end = oldSizes.size(); i != end; ++i) { + oldLength += oldSizes[i]; + if (lookup(modes, i, QHeaderView::Fixed) == QHeaderView::Stretch) { + stretchLength += oldSizes[i]; + ++numStretchSections; + } else { + fixedLength += oldSizes[i]; + } + } + + if (oldLength <= 0) { + hvDebug() << "no existing lengths - returning equidistant sizes"; + return std::vector(oldSizes.size(), newLength / oldSizes.size()); + } + + const int stretchableSpace = std::max(newLength - fixedLength, 0); + + std::vector newSizes; + newSizes.reserve(oldSizes.size()); + for (unsigned int i = 0, end = oldSizes.size(); i != end; ++i) + newSizes.push_back(std::max(minSize, + lookup(modes, i, QHeaderView::Fixed) == QHeaderView::Stretch + ? stretchLength ? stretchableSpace * oldSizes[i] / stretchLength : stretchableSpace / numStretchSections + : oldSizes[i])); + + hvDebug() << "\noldSizes = " << oldSizes << "/" << oldLength + << "\nnewSizes = " << newSizes << "/" << newLength; + + return newSizes; +} +#endif + +void HeaderView::setSectionSizes(const std::vector &sizes) +{ + d->ensureNumSections(sizes.size()); + d->sizes = sizes; + apply_section_sizes(this, sizes); +} + +std::vector HeaderView::sectionSizes() const +{ + return section_sizes(this); +} + +void HeaderView::setSectionResizeMode(unsigned int section, ResizeMode mode) +{ + d->ensureNumSections(section + 1); + d->modes[section] = mode; + if (section < static_cast(count())) { + QHeaderView::setSectionResizeMode(section, mode); + } +} + +#if 0 +void HeaderView::setModel(QAbstractItemModel *model) +{ + + hvDebug() << "before" << section_sizes(this); + + QHeaderView::setModel(model); + + hvDebug() << "after " << section_sizes(this); + +} + +void HeaderView::setRootIndex(const QModelIndex &idx) +{ + hvDebug() << "before" << section_sizes(this); + QHeaderView::setRootIndex(idx); + hvDebug() << "after " << section_sizes(this); +} + +void HeaderView::mousePressEvent(QMouseEvent *e) +{ + d->mousePressed = true; + QHeaderView::mousePressEvent(e); +} + +void HeaderView::mouseReleaseEvent(QMouseEvent *e) +{ + d->mousePressed = false; + QHeaderView::mouseReleaseEvent(e); +} + +void HeaderView::updateGeometries() +{ + + const std::vector oldSizes = d->mousePressed ? section_sizes(this) : d->sizes; + + hvDebug() << "before" << section_sizes(this) << '(' << d->sizes << ')'; + + QHeaderView::updateGeometries(); + + hvDebug() << "after " << section_sizes(this); + + const std::vector newSizes = calculate_section_sizes(oldSizes, width(), d->modes, minimumSectionSize()); + d->sizes = newSizes; + + apply_section_sizes(this, newSizes); +} +#endif + +#include "moc_headerview.cpp" diff --git a/src/ui/headerview.h b/src/ui/headerview.h new file mode 100644 index 0000000..cb48563 --- /dev/null +++ b/src/ui/headerview.h @@ -0,0 +1,71 @@ +/* -*- mode: c++; c-basic-offset:4 -*- + utils/headerview.h + + This file is part of Kleopatra, the KDE keymanager + Copyright (c) 2008 Klarälvdalens Datakonsult AB + + Kleopatra is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + Kleopatra is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + + In addition, as a special exception, the copyright holders give + permission to link the code of this program with any edition of + the Qt library by Trolltech AS, Norway (or with modified versions + of Qt that use the same license as Qt), and distribute linked + combinations including the two. You must obey the GNU General + Public License in all respects for all of the code used other than + Qt. If you modify this file, you may extend this exception to + your version of the file, but you are not obligated to do so. If + you do not wish to do so, delete this exception statement from + your version. +*/ + +#ifndef __KLEOPATRA_UTILS_HEADERVIEW_H__ +#define __KLEOPATRA_UTILS_HEADERVIEW_H__ + +#include + +#include +#include + +namespace Kleo +{ + +class HeaderView : public QHeaderView +{ + Q_OBJECT +public: + explicit HeaderView(Qt::Orientation o, QWidget *parent = nullptr); + ~HeaderView(); + + void setSectionSizes(const std::vector &sizes); + std::vector sectionSizes() const; + + void setSectionResizeMode(unsigned int logicalIndex, ResizeMode mode); + ResizeMode sectionResizeMode(unsigned int logicalIndex) const; +private: + //@{ + /*! Defined, but not implemented, to catch at least some usage errors */ + void setResizeMode(int, ResizeMode); + ResizeMode resizeMode() const; + //@} +private: + class Private; + std::shared_ptr d; + Q_PRIVATE_SLOT(d, void _klhv_slotSectionCountChanged(int, int)) + Q_PRIVATE_SLOT(d, void _klhv_slotSectionResized(int, int, int)) +}; + +} + +#endif /* __KLEOPATRA_UTILS_HEADERVIEW_H__ */ diff --git a/src/ui/keytreeview.cpp b/src/ui/keytreeview.cpp new file mode 100644 index 0000000..3227be4 --- /dev/null +++ b/src/ui/keytreeview.cpp @@ -0,0 +1,710 @@ +/* ui/keytreeview.cpp + + This file is part of Kleopatra, the KDE keymanager + Copyright (c) 2009 Klarälvdalens Datakonsult AB + Copyright (c) 2020 g10 Code GmbH + + Kleopatra is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + Kleopatra is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + + In addition, as a special exception, the copyright holders give + permission to link the code of this program with any edition of + the Qt library by Trolltech AS, Norway (or with modified versions + of Qt that use the same license as Qt), and distribute linked + combinations including the two. You must obey the GNU General + Public License in all respects for all of the code used other than + Qt. If you modify this file, you may extend this exception to + your version of the file, but you are not obligated to do so. If + you do not wish to do so, delete this exception statement from + your version. +*/ + +#include "keytreeview.h" +#include "headerview.h" + +#include "models/keylistmodel.h" +#include "models/keylistsortfilterproxymodel.h" +#include "models/keyrearrangecolumnsproxymodel.h" +#include "models/keycache.h" + +#include "kleo/predicates.h" +#include "kleo/remarks.h" +#include "kleo/stl_util.h" +#include "kleo/keyfilter.h" + +#include + +#include "libkleo_debug.h" +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include + +#include +#if GPGMEPP_VERSION >= 0x10E00 // 1.14.0 +# define GPGME_HAS_REMARKS +#endif + +#define REMARK_COLUMN 13 + +using namespace Kleo; +using namespace GpgME; + +Q_DECLARE_METATYPE(GpgME::Key) + +namespace +{ + +class TreeView : public QTreeView +{ +public: + explicit TreeView(QWidget *parent = nullptr) : QTreeView(parent) + { + header()->installEventFilter(this); + } + + QSize minimumSizeHint() const override + { + const QSize min = QTreeView::minimumSizeHint(); + return QSize(min.width(), min.height() + 5 * fontMetrics().height()); + } + +protected: + bool eventFilter(QObject *watched, QEvent *event) override + { + Q_UNUSED(watched); + if (event->type() == QEvent::ContextMenu) { + QContextMenuEvent *e = static_cast(event); + + if (!mHeaderPopup) { + mHeaderPopup = new QMenu(this); + mHeaderPopup->setTitle(i18n("View Columns")); + for (int i = 0; i < model()->columnCount(); ++i) { + QAction *tmp + = mHeaderPopup->addAction(model()->headerData(i, Qt::Horizontal).toString()); + tmp->setData(QVariant(i)); + tmp->setCheckable(true); + mColumnActions << tmp; + } + + connect(mHeaderPopup, &QMenu::triggered, this, [this] (QAction *action) { + const int col = action->data().toInt(); + if (col == REMARK_COLUMN) { + Remarks::enableRemarks(action->isChecked()); + } + if (action->isChecked()) { + showColumn(col); + } else { + hideColumn(col); + } + + KeyTreeView *tv = qobject_cast (parent()); + if (tv) { + tv->resizeColumns(); + } + }); + } + + foreach (QAction *action, mColumnActions) { + int column = action->data().toInt(); + action->setChecked(!isColumnHidden(column)); + } + + mHeaderPopup->popup(mapToGlobal(e->pos())); + return true; + } + + return false; + } + +private: + QMenu *mHeaderPopup = nullptr; + + QList mColumnActions; +}; + +} // anon namespace + +KeyTreeView::KeyTreeView(QWidget *parent) + : QWidget(parent), + m_proxy(new KeyListSortFilterProxyModel(this)), + m_additionalProxy(nullptr), + m_view(new TreeView(this)), + m_flatModel(nullptr), + m_hierarchicalModel(nullptr), + m_stringFilter(), + m_keyFilter(), + m_isHierarchical(true) +{ + init(); +} + +KeyTreeView::KeyTreeView(const KeyTreeView &other) + : QWidget(nullptr), + m_proxy(new KeyListSortFilterProxyModel(this)), + m_additionalProxy(other.m_additionalProxy ? other.m_additionalProxy->clone() : nullptr), + m_view(new TreeView(this)), + m_flatModel(other.m_flatModel), + m_hierarchicalModel(other.m_hierarchicalModel), + m_stringFilter(other.m_stringFilter), + m_keyFilter(other.m_keyFilter), + m_group(other.m_group), + m_isHierarchical(other.m_isHierarchical) +{ + init(); + setColumnSizes(other.columnSizes()); + setSortColumn(other.sortColumn(), other.sortOrder()); +} + +KeyTreeView::KeyTreeView(const QString &text, const std::shared_ptr &kf, + AbstractKeyListSortFilterProxyModel *proxy, QWidget *parent, + const KConfigGroup &group) + : QWidget(parent), + m_proxy(new KeyListSortFilterProxyModel(this)), + m_additionalProxy(proxy), + m_view(new TreeView(this)), + m_flatModel(nullptr), + m_hierarchicalModel(nullptr), + m_stringFilter(text), + m_keyFilter(kf), + m_group(group), + m_isHierarchical(true), + m_onceResized(false) +{ + init(); +} + +void KeyTreeView::setColumnSizes(const std::vector &sizes) +{ + if (sizes.empty()) { + return; + } + Q_ASSERT(m_view); + Q_ASSERT(m_view->header()); + Q_ASSERT(qobject_cast(m_view->header()) == static_cast(m_view->header())); + if (HeaderView *const hv = static_cast(m_view->header())) { + hv->setSectionSizes(sizes); + } +} + +void KeyTreeView::setSortColumn(int sortColumn, Qt::SortOrder sortOrder) +{ + Q_ASSERT(m_view); + m_view->sortByColumn(sortColumn, sortOrder); +} + +int KeyTreeView::sortColumn() const +{ + Q_ASSERT(m_view); + Q_ASSERT(m_view->header()); + return m_view->header()->sortIndicatorSection(); +} + +Qt::SortOrder KeyTreeView::sortOrder() const +{ + Q_ASSERT(m_view); + Q_ASSERT(m_view->header()); + return m_view->header()->sortIndicatorOrder(); +} + +std::vector KeyTreeView::columnSizes() const +{ + Q_ASSERT(m_view); + Q_ASSERT(m_view->header()); + Q_ASSERT(qobject_cast(m_view->header()) == static_cast(m_view->header())); + if (HeaderView *const hv = static_cast(m_view->header())) { + return hv->sectionSizes(); + } else { + return std::vector(); + } +} + +void KeyTreeView::init() +{ + if (!m_group.isValid()) { + m_group = KSharedConfig::openConfig()->group("KeyTreeView_default"); + } else { + // Reopen as non const + KConfig *conf = m_group.config(); + m_group = conf->group(m_group.name()); + } + + QLayout *layout = new QVBoxLayout(this); + layout->setContentsMargins(0, 0, 0, 0); + layout->addWidget(m_view); + + HeaderView *headerView = new HeaderView(Qt::Horizontal); + headerView->installEventFilter(m_view); + headerView->setSectionsMovable(true); + m_view->setHeader(headerView); + + m_view->setSelectionBehavior(QAbstractItemView::SelectRows); + m_view->setSelectionMode(QAbstractItemView::ExtendedSelection); + //m_view->setAlternatingRowColors( true ); + m_view->setAllColumnsShowFocus(true); + m_view->setSortingEnabled(true); + + if (model()) { + if (m_additionalProxy) { + m_additionalProxy->setSourceModel(model()); + } else { + m_proxy->setSourceModel(model()); + } + } + if (m_additionalProxy) { + m_proxy->setSourceModel(m_additionalProxy); + if (!m_additionalProxy->parent()) { + m_additionalProxy->setParent(this); + } + } + + m_proxy->setFilterFixedString(m_stringFilter); + m_proxy->setKeyFilter(m_keyFilter); + m_proxy->setSortCaseSensitivity(Qt::CaseInsensitive); + + KeyRearrangeColumnsProxyModel *rearangingModel = new KeyRearrangeColumnsProxyModel(this); + rearangingModel->setSourceModel(m_proxy); + rearangingModel->setSourceColumns(QVector() << KeyListModelInterface::PrettyName + << KeyListModelInterface::PrettyEMail + << KeyListModelInterface::Validity + << KeyListModelInterface::ValidFrom + << KeyListModelInterface::ValidUntil + << KeyListModelInterface::TechnicalDetails + << KeyListModelInterface::KeyID + << KeyListModelInterface::Fingerprint + << KeyListModelInterface::OwnerTrust + << KeyListModelInterface::Origin + << KeyListModelInterface::LastUpdate + << KeyListModelInterface::Issuer + << KeyListModelInterface::SerialNumber +#ifdef GPGME_HAS_REMARKS + // If a column is added before this REMARK_COLUMN define has to be updated accordingly + << KeyListModelInterface::Remarks +#endif + ); + m_view->setModel(rearangingModel); + + /* Handle expansion state */ + m_expandedKeys = m_group.readEntry("Expanded", QStringList()); + + connect(m_view, &QTreeView::expanded, this, [this] (const QModelIndex &index) { + if (!index.isValid()) { + return; + } + const auto &key = index.data(Kleo::KeyListModelInterface::KeyRole).value(); + const auto fpr = QString::fromLatin1(key.primaryFingerprint()); + + if (m_expandedKeys.contains(fpr)) { + return; + } + m_expandedKeys << fpr; + m_group.writeEntry("Expanded", m_expandedKeys); + }); + + connect(m_view, &QTreeView::collapsed, this, [this] (const QModelIndex &index) { + if (!index.isValid()) { + return; + } + const auto &key = index.data(Kleo::KeyListModelInterface::KeyRole).value(); + m_expandedKeys.removeAll(QString::fromLatin1(key.primaryFingerprint())); + m_group.writeEntry("Expanded", m_expandedKeys); + }); + + connect(KeyCache::instance().get(), &KeyCache::keysMayHaveChanged, this, [this] () { + /* We use a single shot timer here to ensure that the keysMayHaveChanged + * handlers are all handled before we restore the expand state so that + * the model is already populated. */ + QTimer::singleShot(0, [this] () { + restoreExpandState(); + setupRemarkKeys(); + if (!m_onceResized) { + m_onceResized = true; + resizeColumns(); + } + }); + }); + resizeColumns(); + restoreLayout(); +} + +void KeyTreeView::restoreExpandState() +{ + if (!KeyCache::instance()->initialized()) { + qCWarning(LIBKLEO_LOG) << "Restore expand state before keycache available. Aborting."; + return; + } + for (const auto &fpr: m_expandedKeys) { + const KeyListModelInterface *km = dynamic_cast (m_view->model()); + if (!km) { + qCWarning(LIBKLEO_LOG) << "invalid model"; + return; + } + const auto key = KeyCache::instance()->findByFingerprint(fpr.toLatin1().constData()); + if (key.isNull()) { + qCDebug(LIBKLEO_LOG) << "Cannot find:" << fpr << "anymore in cache"; + m_expandedKeys.removeAll(fpr); + return; + } + const auto idx = km->index(key); + if (!idx.isValid()) { + qCDebug(LIBKLEO_LOG) << "Cannot find:" << fpr << "anymore in model"; + m_expandedKeys.removeAll(fpr); + return; + } + m_view->expand(idx); + } +} + +void KeyTreeView::setupRemarkKeys() +{ +#ifdef GPGME_HAS_REMARKS + const auto remarkKeys = Remarks::remarkKeys(); + if (m_hierarchicalModel) { + m_hierarchicalModel->setRemarkKeys(remarkKeys); + } + if (m_flatModel) { + m_flatModel->setRemarkKeys(remarkKeys); + } +#endif +} + +void KeyTreeView::saveLayout() +{ + QHeaderView *header = m_view->header(); + + QVariantList columnVisibility; + QVariantList columnOrder; + QVariantList columnWidths; + const int headerCount = header->count(); + columnVisibility.reserve(headerCount); + columnWidths.reserve(headerCount); + columnOrder.reserve(headerCount); + for (int i = 0; i < headerCount; ++i) { + columnVisibility << QVariant(!m_view->isColumnHidden(i)); + columnWidths << QVariant(header->sectionSize(i)); + columnOrder << QVariant(header->visualIndex(i)); + } + + m_group.writeEntry("ColumnVisibility", columnVisibility); + m_group.writeEntry("ColumnOrder", columnOrder); + m_group.writeEntry("ColumnWidths", columnWidths); + + m_group.writeEntry("SortAscending", (int)header->sortIndicatorOrder()); + if (header->isSortIndicatorShown()) { + m_group.writeEntry("SortColumn", header->sortIndicatorSection()); + } else { + m_group.writeEntry("SortColumn", -1); + } +} + +void KeyTreeView::restoreLayout() +{ + QHeaderView *header = m_view->header(); + + QVariantList columnVisibility = m_group.readEntry("ColumnVisibility", QVariantList()); + QVariantList columnOrder = m_group.readEntry("ColumnOrder", QVariantList()); + QVariantList columnWidths = m_group.readEntry("ColumnWidths", QVariantList()); + + if (columnVisibility.isEmpty()) { + // if config is empty then use default settings + // The numbers have to be in line with the order in + // setsSourceColumns above + m_view->hideColumn(5); + + for (int i = 7; i < m_view->model()->columnCount(); ++i) { + m_view->hideColumn(i); + } + if (KeyCache::instance()->initialized()) { + QTimer::singleShot(0, this, &KeyTreeView::resizeColumns); + } + } else { + for (int i = 0; i < header->count(); ++i) { + if (i >= columnOrder.size() || i >= columnWidths.size() || i >= columnVisibility.size()) { + // An additional column that was not around last time we saved. + // We default to hidden. + m_view->hideColumn(i); + continue; + } + bool visible = columnVisibility[i].toBool(); + int width = columnWidths[i].toInt(); + int order = columnOrder[i].toInt(); + + header->resizeSection(i, width ? width : 100); + header->moveSection(header->visualIndex(i), order); + if (i == REMARK_COLUMN) { + Remarks::enableRemarks(visible); + } + if (!visible) { + m_view->hideColumn(i); + } + } + m_onceResized = true; + } + + int sortOrder = m_group.readEntry("SortAscending", (int)Qt::AscendingOrder); + int sortColumn = m_group.readEntry("SortColumn", -1); + if (sortColumn >= 0) { + m_view->sortByColumn(sortColumn, (Qt::SortOrder)sortOrder); + } +} + +KeyTreeView::~KeyTreeView() +{ + saveLayout(); +} + +static QAbstractProxyModel *find_last_proxy(QAbstractProxyModel *pm) +{ + Q_ASSERT(pm); + while (QAbstractProxyModel *const sm = qobject_cast(pm->sourceModel())) { + pm = sm; + } + return pm; +} + +void KeyTreeView::setFlatModel(AbstractKeyListModel *model) +{ + if (model == m_flatModel) { + return; + } + m_flatModel = model; + if (!m_isHierarchical) + // TODO: this fails when called after setHierarchicalView( false )... + { + find_last_proxy(m_proxy)->setSourceModel(model); + } +} + +void KeyTreeView::setHierarchicalModel(AbstractKeyListModel *model) +{ + if (model == m_hierarchicalModel) { + return; + } + m_hierarchicalModel = model; + if (m_isHierarchical) { + find_last_proxy(m_proxy)->setSourceModel(model); + m_view->expandAll(); + for (int column = 0; column < m_view->header()->count(); ++column) { + m_view->header()->resizeSection(column, qMax(m_view->header()->sectionSize(column), m_view->header()->sectionSizeHint(column))); + } + } +} + +void KeyTreeView::setStringFilter(const QString &filter) +{ + if (filter == m_stringFilter) { + return; + } + m_stringFilter = filter; + m_proxy->setFilterFixedString(filter); + Q_EMIT stringFilterChanged(filter); +} + +void KeyTreeView::setKeyFilter(const std::shared_ptr &filter) +{ + if (filter == m_keyFilter || (filter && m_keyFilter && filter->id() == m_keyFilter->id())) { + return; + } + m_keyFilter = filter; + m_proxy->setKeyFilter(filter); + Q_EMIT keyFilterChanged(filter); +} + +static QItemSelection itemSelectionFromKeys(const std::vector &keys, const KeyListSortFilterProxyModel &proxy) +{ + QItemSelection result; + for (const Key &key : keys) { + const QModelIndex mi = proxy.index(key); + if (mi.isValid()) { + result.merge(QItemSelection(mi, mi), QItemSelectionModel::Select); + } + } + return result; +} + +void KeyTreeView::selectKeys(const std::vector &keys) +{ + m_view->selectionModel()->select(itemSelectionFromKeys(keys, *m_proxy), QItemSelectionModel::ClearAndSelect | QItemSelectionModel::Rows); +} + +std::vector KeyTreeView::selectedKeys() const +{ + return m_proxy->keys(m_view->selectionModel()->selectedRows()); +} + +void KeyTreeView::setHierarchicalView(bool on) +{ + if (on == m_isHierarchical) { + return; + } + if (on && !hierarchicalModel()) { + qCWarning(LIBKLEO_LOG) << "hierarchical view requested, but no hierarchical model set"; + return; + } + if (!on && !flatModel()) { + qCWarning(LIBKLEO_LOG) << "flat view requested, but no flat model set"; + return; + } + const std::vector selectedKeys = m_proxy->keys(m_view->selectionModel()->selectedRows()); + const Key currentKey = m_proxy->key(m_view->currentIndex()); + + m_isHierarchical = on; + find_last_proxy(m_proxy)->setSourceModel(model()); + if (on) { + m_view->expandAll(); + } + selectKeys(selectedKeys); + if (!currentKey.isNull()) { + const QModelIndex currentIndex = m_proxy->index(currentKey); + if (currentIndex.isValid()) { + m_view->selectionModel()->setCurrentIndex(m_proxy->index(currentKey), QItemSelectionModel::NoUpdate); + m_view->scrollTo(currentIndex); + } + } + Q_EMIT hierarchicalChanged(on); +} + +void KeyTreeView::setKeys(const std::vector &keys) +{ + std::vector sorted = keys; + _detail::sort_by_fpr(sorted); + _detail::remove_duplicates_by_fpr(sorted); + m_keys = sorted; + if (m_flatModel) { + m_flatModel->setKeys(sorted); + } + if (m_hierarchicalModel) { + m_hierarchicalModel->setKeys(sorted); + } +} + +void KeyTreeView::addKeysImpl(const std::vector &keys, bool select) +{ + if (keys.empty()) { + return; + } + if (m_keys.empty()) { + setKeys(keys); + return; + } + + std::vector sorted = keys; + _detail::sort_by_fpr(sorted); + _detail::remove_duplicates_by_fpr(sorted); + + std::vector newKeys = _detail::union_by_fpr(sorted, m_keys); + m_keys.swap(newKeys); + + if (m_flatModel) { + m_flatModel->addKeys(sorted); + } + if (m_hierarchicalModel) { + m_hierarchicalModel->addKeys(sorted); + } + + if (select) { + selectKeys(sorted); + } +} + +void KeyTreeView::addKeysSelected(const std::vector &keys) +{ + addKeysImpl(keys, true); +} + +void KeyTreeView::addKeysUnselected(const std::vector &keys) +{ + addKeysImpl(keys, false); +} + +void KeyTreeView::removeKeys(const std::vector &keys) +{ + if (keys.empty()) { + return; + } + std::vector sorted = keys; + _detail::sort_by_fpr(sorted); + _detail::remove_duplicates_by_fpr(sorted); + std::vector newKeys; + newKeys.reserve(m_keys.size()); + std::set_difference(m_keys.begin(), m_keys.end(), + sorted.begin(), sorted.end(), + std::back_inserter(newKeys), + _detail::ByFingerprint()); + m_keys.swap(newKeys); + + if (m_flatModel) { + std::for_each(sorted.cbegin(), sorted.cend(), + [this](const Key &key) { m_flatModel->removeKey(key); }); + } + if (m_hierarchicalModel) { + std::for_each(sorted.cbegin(), sorted.cend(), + [this](const Key &key) { m_hierarchicalModel->removeKey(key); }); + } + +} + +static const struct { + const char *signal; + const char *slot; +} connections[] = { + { + SIGNAL(stringFilterChanged(QString)), + SLOT(setStringFilter(QString)) + }, + { + SIGNAL(keyFilterChanged(std::shared_ptr)), + SLOT(setKeyFilter(std::shared_ptr)) + }, +}; +static const unsigned int numConnections = sizeof connections / sizeof * connections; + +void KeyTreeView::disconnectSearchBar(const QObject *bar) +{ + for (unsigned int i = 0; i < numConnections; ++i) { + disconnect(this, connections[i].signal, bar, connections[i].slot); + disconnect(bar, connections[i].signal, this, connections[i].slot); + } +} + +bool KeyTreeView::connectSearchBar(const QObject *bar) +{ + for (unsigned int i = 0; i < numConnections; ++i) + if (!connect(this, connections[i].signal, bar, connections[i].slot) || + !connect(bar, connections[i].signal, this, connections[i].slot)) { + return false; + } + return true; +} + +void KeyTreeView::resizeColumns() +{ + m_view->setColumnWidth(KeyListModelInterface::PrettyName, 260); + m_view->setColumnWidth(KeyListModelInterface::PrettyEMail, 260); + + for (int i = 2; i < m_view->model()->columnCount(); ++i) { + m_view->resizeColumnToContents(i); + } +} diff --git a/src/ui/keytreeview.h b/src/ui/keytreeview.h new file mode 100644 index 0000000..2980fec --- /dev/null +++ b/src/ui/keytreeview.h @@ -0,0 +1,183 @@ +/* ui/keytreeview.h + + This file is part of Kleopatra, the KDE keymanager + Copyright (c) 2009 Klarälvdalens Datakonsult AB + + Kleopatra is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + Kleopatra is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + + In addition, as a special exception, the copyright holders give + permission to link the code of this program with any edition of + the Qt library by Trolltech AS, Norway (or with modified versions + of Qt that use the same license as Qt), and distribute linked + combinations including the two. You must obey the GNU General + Public License in all respects for all of the code used other than + Qt. If you modify this file, you may extend this exception to + your version of the file, but you are not obligated to do so. If + you do not wish to do so, delete this exception statement from + your version. +*/ + +#ifndef __KLEOPATRA_VIEW_KEYTREEVIEW_H__ +#define __KLEOPATRA_VIEW_KEYTREEVIEW_H__ + +#include + +#include +#include + +#include + +#include +#include + +#include + +#include "kleo_export.h" + +class QTreeView; + +namespace Kleo +{ + +class KeyFilter; +class AbstractKeyListModel; +class AbstractKeyListSortFilterProxyModel; +class KeyListSortFilterProxyModel; + +class KLEO_EXPORT KeyTreeView : public QWidget +{ + Q_OBJECT +public: + explicit KeyTreeView(QWidget *parent = nullptr); + KeyTreeView(const QString &stringFilter, const std::shared_ptr &keyFilter, + AbstractKeyListSortFilterProxyModel *additionalProxy, QWidget *parent, + const KConfigGroup &group = KConfigGroup()); + ~KeyTreeView(); + + QTreeView *view() const + { + return m_view; + } + + AbstractKeyListModel *model() const + { + return m_isHierarchical ? hierarchicalModel() : flatModel(); + } + + AbstractKeyListModel *flatModel() const + { + return m_flatModel; + } + AbstractKeyListModel *hierarchicalModel() const + { + return m_hierarchicalModel; + } + + void setFlatModel(AbstractKeyListModel *model); + void setHierarchicalModel(AbstractKeyListModel *model); + + void setKeys(const std::vector &keys); + const std::vector &keys() const + { + return m_keys; + } + + void selectKeys(const std::vector &keys); + std::vector selectedKeys() const; + + void addKeysUnselected(const std::vector &keys); + void addKeysSelected(const std::vector &keys); + void removeKeys(const std::vector &keys); + +#if 0 + void setToolTipOptions(int options); + int toolTipOptions() const; +#endif + + QString stringFilter() const + { + return m_stringFilter; + } + const std::shared_ptr &keyFilter() const + { + return m_keyFilter; + } + bool isHierarchicalView() const + { + return m_isHierarchical; + } + + void setColumnSizes(const std::vector &sizes); + std::vector columnSizes() const; + + void setSortColumn(int sortColumn, Qt::SortOrder sortOrder); + int sortColumn() const; + Qt::SortOrder sortOrder() const; + + virtual KeyTreeView *clone() const + { + return new KeyTreeView(*this); + } + + void disconnectSearchBar(const QObject *bar); + bool connectSearchBar(const QObject *bar); + void resizeColumns(); + +public Q_SLOTS: + virtual void setStringFilter(const QString &text); + virtual void setKeyFilter(const std::shared_ptr &filter); + virtual void setHierarchicalView(bool on); + +Q_SIGNALS: + void stringFilterChanged(const QString &filter); + void keyFilterChanged(const std::shared_ptr &filter); + void hierarchicalChanged(bool on); + +protected: + KeyTreeView(const KeyTreeView &); + +private: + void init(); + void addKeysImpl(const std::vector &, bool); + void restoreExpandState(); + void saveLayout(); + void restoreLayout(); + void setupRemarkKeys(); + +private: + std::vector m_keys; + + KeyListSortFilterProxyModel *m_proxy; + AbstractKeyListSortFilterProxyModel *m_additionalProxy; + + QTreeView *m_view; + + AbstractKeyListModel *m_flatModel; + AbstractKeyListModel *m_hierarchicalModel; + + QString m_stringFilter; + std::shared_ptr m_keyFilter; + + QStringList m_expandedKeys; + + KConfigGroup m_group; + + bool m_isHierarchical : 1; + bool m_onceResized : 1; +}; + +} + +#endif // __KLEOPATRA_VIEW_KEYTREEVIEW_H__