diff --git a/src/view/keylistcontroller.cpp b/src/view/keylistcontroller.cpp index c7c4ebe7f..01732ffc8 100644 --- a/src/view/keylistcontroller.cpp +++ b/src/view/keylistcontroller.cpp @@ -1,862 +1,853 @@ /* -*- mode: c++; c-basic-offset:4 -*- controllers/keylistcontroller.cpp This file is part of Kleopatra, the KDE keymanager SPDX-FileCopyrightText: 2007 Klarälvdalens Datakonsult AB SPDX-FileCopyrightText: 2022 Felix Tiede SPDX-License-Identifier: GPL-2.0-or-later */ #include #include "keylistcontroller.h" #include "tabwidget.h" #include #include #include #include "tooltippreferences.h" #include "kleopatra_debug.h" #include "commands/exportcertificatecommand.h" #include "commands/exportopenpgpcertstoservercommand.h" #ifdef MAILAKONADI_ENABLED #include "commands/exportopenpgpcerttoprovidercommand.h" #endif // MAILAKONADI_ENABLED #ifdef QGPGME_SUPPORTS_SECRET_KEY_EXPORT # include "commands/exportsecretkeycommand.h" #else # include "commands/exportsecretkeycommand_old.h" #endif #include "commands/importcertificatefromfilecommand.h" #include "commands/changepassphrasecommand.h" #include "commands/lookupcertificatescommand.h" #include "commands/reloadkeyscommand.h" #include "commands/refreshx509certscommand.h" #include "commands/refreshopenpgpcertscommand.h" #include "commands/detailscommand.h" #include "commands/deletecertificatescommand.h" #include "commands/decryptverifyfilescommand.h" #include "commands/signencryptfilescommand.h" #include "commands/signencryptfoldercommand.h" #include "commands/clearcrlcachecommand.h" #include "commands/dumpcrlcachecommand.h" #include "commands/dumpcertificatecommand.h" #include "commands/importcrlcommand.h" #include "commands/changeexpirycommand.h" #include "commands/changeownertrustcommand.h" #include "commands/changeroottrustcommand.h" #include "commands/certifycertificatecommand.h" #include "commands/revokecertificationcommand.h" #include "commands/adduseridcommand.h" #include "commands/newcertificatecommand.h" #include "commands/checksumverifyfilescommand.h" #include "commands/checksumcreatefilescommand.h" #include "commands/exportpaperkeycommand.h" #include "commands/revokekeycommand.h" #include #include #include #include #include #include #include #include #include #include #include #include // needed for GPGME_VERSION_NUMBER #include using namespace Kleo; using namespace Kleo::Commands; using namespace Kleo::SmartCard; using namespace GpgME; #ifndef QGPGME_SUPPORTS_SECRET_KEY_EXPORT using Kleo::Commands::Compat::ExportSecretKeyCommand; #endif class KeyListController::Private { friend class ::Kleo::KeyListController; KeyListController *const q; public: explicit Private(KeyListController *qq); ~Private(); void connectView(QAbstractItemView *view); void connectCommand(Command *cmd); void connectTabWidget(); void disconnectTabWidget(); void addCommand(Command *cmd) { connectCommand(cmd); commands.insert(std::lower_bound(commands.begin(), commands.end(), cmd), cmd); } void addView(QAbstractItemView *view) { connectView(view); views.insert(std::lower_bound(views.begin(), views.end(), view), view); } void removeView(QAbstractItemView *view) { view->disconnect(q); view->selectionModel()->disconnect(q); views.erase(std::remove(views.begin(), views.end(), view), views.end()); } public: void slotDestroyed(QObject *o) { qCDebug(KLEOPATRA_LOG) << (void *)o; views.erase(std::remove(views.begin(), views.end(), o), views.end()); commands.erase(std::remove(commands.begin(), commands.end(), o), commands.end()); } void slotDoubleClicked(const QModelIndex &idx); void slotActivated(const QModelIndex &idx); void slotSelectionChanged(const QItemSelection &old, const QItemSelection &new_); void slotContextMenu(const QPoint &pos); void slotCommandFinished(); void slotAddKey(const Key &key); void slotAboutToRemoveKey(const Key &key); void slotProgress(const QString &what, int current, int total) { Q_EMIT q->progress(current, total); if (!what.isEmpty()) { Q_EMIT q->message(what); } } - void slotActionTriggered(); + void slotActionTriggered(QAction *action); void slotCurrentViewChanged(QAbstractItemView *view) { if (view && !std::binary_search(views.cbegin(), views.cend(), view)) { qCDebug(KLEOPATRA_LOG) << "you need to register view" << view << "before trying to set it as the current view!"; addView(view); } currentView = view; q->enableDisableActions(view ? view->selectionModel() : nullptr); } private: int toolTipOptions() const; private: static Command::Restrictions calculateRestrictionsMask(const QItemSelectionModel *sm); private: struct action_item { QPointer action; Command::Restrictions restrictions; Command *(*createCommand)(QAbstractItemView *, KeyListController *); }; std::vector actions; std::vector views; std::vector commands; QPointer parentWidget; QPointer tabWidget; QPointer currentView; QPointer flatModel, hierarchicalModel; + std::vector m_connections; }; KeyListController::Private::Private(KeyListController *qq) : q(qq), actions(), views(), commands(), parentWidget(), tabWidget(), flatModel(), hierarchicalModel() { connect(KeyCache::instance().get(), &KeyCache::added, q, [this](const GpgME::Key &key) { slotAddKey(key); }); connect(KeyCache::instance().get(), &KeyCache::aboutToRemove, q, [this](const GpgME::Key &key) { slotAboutToRemoveKey(key); }); } KeyListController::Private::~Private() {} KeyListController::KeyListController(QObject *p) : QObject(p), d(new Private(this)) { } KeyListController::~KeyListController() {} void KeyListController::Private::slotAddKey(const Key &key) { // ### make model act on keycache directly... if (flatModel) { flatModel->addKey(key); } if (hierarchicalModel) { hierarchicalModel->addKey(key); } } void KeyListController::Private::slotAboutToRemoveKey(const Key &key) { // ### make model act on keycache directly... if (flatModel) { flatModel->removeKey(key); } if (hierarchicalModel) { hierarchicalModel->removeKey(key); } } void KeyListController::addView(QAbstractItemView *view) { if (!view || std::binary_search(d->views.cbegin(), d->views.cend(), view)) { return; } d->addView(view); } void KeyListController::removeView(QAbstractItemView *view) { if (!view || !std::binary_search(d->views.cbegin(), d->views.cend(), view)) { return; } d->removeView(view); } void KeyListController::setCurrentView(QAbstractItemView *view) { d->slotCurrentViewChanged(view); } std::vector KeyListController::views() const { return d->views; } void KeyListController::setFlatModel(AbstractKeyListModel *model) { if (model == d->flatModel) { return; } d->flatModel = model; if (model) { model->clear(); if (KeyCache::instance()->initialized()) { model->addKeys(KeyCache::instance()->keys()); } model->setToolTipOptions(d->toolTipOptions()); } } void KeyListController::setHierarchicalModel(AbstractKeyListModel *model) { if (model == d->hierarchicalModel) { return; } d->hierarchicalModel = model; if (model) { model->clear(); if (KeyCache::instance()->initialized()) { model->addKeys(KeyCache::instance()->keys()); } model->setToolTipOptions(d->toolTipOptions()); } } void KeyListController::setTabWidget(TabWidget *tabWidget) { if (tabWidget == d->tabWidget) { return; } d->disconnectTabWidget(); d->tabWidget = tabWidget; d->connectTabWidget(); d->slotCurrentViewChanged(tabWidget ? tabWidget->currentView() : nullptr); } void KeyListController::setParentWidget(QWidget *parent) { d->parentWidget = parent; } QWidget *KeyListController::parentWidget() const { return d->parentWidget; } -static const struct { - const char *signal; - const char *slot; -} tabs2controller[] = { - { SIGNAL(viewAdded(QAbstractItemView*)), SLOT(addView(QAbstractItemView*)) }, - { SIGNAL(viewAboutToBeRemoved(QAbstractItemView*)), SLOT(removeView(QAbstractItemView*)) }, - { SIGNAL(currentViewChanged(QAbstractItemView*)), SLOT(slotCurrentViewChanged(QAbstractItemView*)) }, -}; -static const unsigned int numTabs2Controller = sizeof tabs2controller / sizeof * tabs2controller; - void KeyListController::Private::connectTabWidget() { if (!tabWidget) { return; } const auto views = tabWidget->views(); std::for_each(views.cbegin(), views.cend(), [this](QAbstractItemView *view) { addView(view); }); - for (unsigned int i = 0; i < numTabs2Controller; ++i) { - connect(tabWidget, tabs2controller[i].signal, q, tabs2controller[i].slot); - } + + m_connections.reserve(3); + m_connections.push_back(connect(tabWidget, &TabWidget::viewAdded, q, [this](QAbstractItemView *view) { addView(view);})); + m_connections.push_back(connect(tabWidget, &TabWidget::viewAboutToBeRemoved, q, [this](QAbstractItemView *view) { removeView(view); })); + m_connections.push_back(connect(tabWidget, &TabWidget::currentViewChanged, q, [this](QAbstractItemView *view) { slotCurrentViewChanged(view); })); } void KeyListController::Private::disconnectTabWidget() { if (!tabWidget) { return; } - for (unsigned int i = 0; i < numTabs2Controller; ++i) { - disconnect(tabWidget, tabs2controller[i].signal, q, tabs2controller[i].slot); + for (const auto &connection : m_connections) { + disconnect(connection); } + m_connections.clear(); + const auto views = tabWidget->views(); std::for_each(views.cbegin(), views.cend(), [this](QAbstractItemView *view) { removeView(view); }); } AbstractKeyListModel *KeyListController::flatModel() const { return d->flatModel; } AbstractKeyListModel *KeyListController::hierarchicalModel() const { return d->hierarchicalModel; } QAbstractItemView *KeyListController::currentView() const { return d->currentView; } TabWidget *KeyListController::tabWidget() const { return d->tabWidget; } void KeyListController::createActions(KActionCollection *coll) { const std::vector common_and_openpgp_action_data = { // File menu { "file_new_certificate", i18n("New Key Pair..."), QString(), "view-certificate-add", nullptr, nullptr, QStringLiteral("Ctrl+N"), false, true }, { "file_export_certificates", i18n("Export..."), i18n("Export the selected certificate (public key) to a file"), "view-certificate-export", nullptr, nullptr, QStringLiteral("Ctrl+E"), false, true }, { "file_export_certificates_to_server", i18n("Publish on Server..."), i18n("Publish the selected certificate (public key) on a public keyserver"), "view-certificate-export-server", nullptr, nullptr, QStringLiteral("Ctrl+Shift+E"), false, true }, #ifdef MAILAKONADI_ENABLED { "file_export_certificate_to_provider", i18n("Publish at Mail Provider..."), i18n("Publish the selected certificate (public key) at mail provider's Web Key Directory if offered"), "view-certificate-export", nullptr, nullptr, QString(), false, true }, #endif // MAILAKONADI_ENABLED { "file_export_secret_keys", i18n("Backup Secret Keys..."), QString(), "view-certificate-export-secret", nullptr, nullptr, QString(), false, true }, { "file_export_paper_key", i18n("Print Secret Key..."), QString(), "document-print", nullptr, nullptr, QString(), false, true }, { "file_lookup_certificates", i18n("Lookup on Server..."), i18n("Search for certificates online using a public keyserver"), "edit-find", nullptr, nullptr, QStringLiteral("Shift+Ctrl+I"), false, true }, { "file_import_certificates", i18n("Import..."), i18n("Import a certificate from a file"), "view-certificate-import", nullptr, nullptr, QStringLiteral("Ctrl+I"), false, true }, { "file_decrypt_verify_files", i18n("Decrypt/Verify..."), i18n("Decrypt and/or verify files"), "document-edit-decrypt-verify", nullptr, nullptr, QString(), false, true }, { "file_sign_encrypt_files", i18n("Sign/Encrypt..."), i18n("Encrypt and/or sign files"), "document-edit-sign-encrypt", nullptr, nullptr, QString(), false, true }, { "file_sign_encrypt_folder", i18n("Sign/Encrypt Folder..."), i18n("Encrypt and/or sign folders"), nullptr/*"folder-edit-sign-encrypt"*/, nullptr, nullptr, QString(), false, true }, { "file_checksum_create_files", i18n("Create Checksum Files..."), QString(), nullptr/*"document-checksum-create"*/, nullptr, nullptr, QString(), false, true }, { "file_checksum_verify_files", i18n("Verify Checksum Files..."), QString(), nullptr/*"document-checksum-verify"*/, nullptr, nullptr, QString(), false, true }, // View menu { "view_redisplay", i18n("Redisplay"), QString(), "view-refresh", nullptr, nullptr, QStringLiteral("F5"), false, true }, { "view_stop_operations", i18n("Stop Operation"), QString(), "process-stop", this, SLOT(cancelCommands()), QStringLiteral("Escape"), false, false }, { "view_certificate_details", i18n("Details"), QString(), "dialog-information", nullptr, nullptr, QString(), false, true }, // Certificate menu #ifdef QGPGME_SUPPORTS_KEY_REVOCATION { "certificates_revoke", i18n("Revoke Certificate..."), i18n("Revoke the selected OpenPGP certificate"), "view-certificate-revoke", nullptr, nullptr, {}, false, true }, #endif { "certificates_delete", i18n("Delete"), i18n("Delete selected certificates"), "edit-delete", nullptr, nullptr, QStringLiteral("Delete"), false, true }, { "certificates_certify_certificate", i18n("Certify..."), i18n("Certify the validity of the selected certificate"), "view-certificate-sign", nullptr, nullptr, QString(), false, true }, { "certificates_revoke_certification", i18n("Revoke Certification..."), i18n("Revoke the certification of the selected certificate"), "view-certificate-revoke", nullptr, nullptr, QString(), false, true }, { "certificates_change_expiry", i18n("Change Expiry Date..."), QString(), nullptr, nullptr, nullptr, QString(), false, true }, { "certificates_change_owner_trust", i18n("Change Certification Trust..."), QString(), nullptr, nullptr, nullptr, QString(), false, true }, { "certificates_change_passphrase", i18n("Change Passphrase..."), QString(), nullptr, nullptr, nullptr, QString(), false, true }, { "certificates_add_userid", i18n("Add User ID..."), QString(), nullptr, nullptr, nullptr, QString(), false, true }, // Tools menu { "tools_refresh_openpgp_certificates", i18n("Refresh OpenPGP Certificates"), QString(), "view-refresh", nullptr, nullptr, QString(), false, true }, // Window menu // (come from TabWidget) // Help menu // (come from MainWindow) }; static const std::vector cms_action_data = { // Certificate menu { "certificates_trust_root", i18n("Trust Root Certificate"), QString(), nullptr, nullptr, nullptr, QString(), false, true }, { "certificates_distrust_root", i18n("Distrust Root Certificate"), QString(), nullptr, nullptr, nullptr, QString(), false, true }, { "certificates_dump_certificate", i18n("Technical Details"), QString(), nullptr, nullptr, nullptr, QString(), false, true }, // Tools menu { "tools_refresh_x509_certificates", i18n("Refresh S/MIME Certificates"), QString(), "view-refresh", nullptr, nullptr, QString(), false, true }, { "crl_clear_crl_cache", i18n("Clear CRL Cache"), QString(), nullptr, nullptr, nullptr, QString(), false, true }, { "crl_dump_crl_cache", i18n("Dump CRL Cache"), QString(), nullptr, nullptr, nullptr, QString(), false, true }, { "crl_import_crl", i18n("Import CRL From File..."), QString(), nullptr, nullptr, nullptr, QString(), false, true }, }; std::vector action_data = common_and_openpgp_action_data; if (Settings{}.cmsEnabled()) { action_data.reserve(action_data.size() + cms_action_data.size()); std::copy(std::begin(cms_action_data), std::end(cms_action_data), std::back_inserter(action_data)); } make_actions_from_data(action_data, coll); if (QAction *action = coll->action(QStringLiteral("view_stop_operations"))) { connect(this, &KeyListController::commandsExecuting, action, &QAction::setEnabled); } // ### somehow make this better... registerActionForCommand(coll->action(QStringLiteral("file_new_certificate"))); //--- registerActionForCommand(coll->action(QStringLiteral("file_lookup_certificates"))); registerActionForCommand(coll->action(QStringLiteral("file_import_certificates"))); //--- registerActionForCommand(coll->action(QStringLiteral("file_export_certificates"))); registerActionForCommand(coll->action(QStringLiteral("file_export_secret_keys"))); registerActionForCommand(coll->action(QStringLiteral("file_export_paper_key"))); registerActionForCommand(coll->action(QStringLiteral("file_export_certificates_to_server"))); #ifdef MAILAKONADI_ENABLED registerActionForCommand(coll->action(QStringLiteral("file_export_certificate_to_provider"))); #endif // MAILAKONADI_ENABLED //--- registerActionForCommand(coll->action(QStringLiteral("file_decrypt_verify_files"))); registerActionForCommand(coll->action(QStringLiteral("file_sign_encrypt_files"))); registerActionForCommand(coll->action(QStringLiteral("file_sign_encrypt_folder"))); //--- registerActionForCommand(coll->action(QStringLiteral("file_checksum_create_files"))); registerActionForCommand(coll->action(QStringLiteral("file_checksum_verify_files"))); registerActionForCommand(coll->action(QStringLiteral("view_redisplay"))); //coll->action( "view_stop_operations" ) <-- already dealt with in make_actions_from_data() registerActionForCommand(coll->action(QStringLiteral("view_certificate_details"))); registerActionForCommand(coll->action(QStringLiteral("certificates_change_owner_trust"))); registerActionForCommand(coll->action(QStringLiteral("certificates_trust_root"))); registerActionForCommand(coll->action(QStringLiteral("certificates_distrust_root"))); //--- registerActionForCommand(coll->action(QStringLiteral("certificates_certify_certificate"))); if (RevokeCertificationCommand::isSupported()) { registerActionForCommand(coll->action(QStringLiteral("certificates_revoke_certification"))); } //--- registerActionForCommand(coll->action(QStringLiteral("certificates_change_expiry"))); registerActionForCommand(coll->action(QStringLiteral("certificates_change_passphrase"))); registerActionForCommand(coll->action(QStringLiteral("certificates_add_userid"))); //--- #ifdef QGPGME_SUPPORTS_KEY_REVOCATION registerActionForCommand(coll->action(QStringLiteral("certificates_revoke"))); #endif registerActionForCommand(coll->action(QStringLiteral("certificates_delete"))); //--- registerActionForCommand(coll->action(QStringLiteral("certificates_dump_certificate"))); registerActionForCommand(coll->action(QStringLiteral("tools_refresh_x509_certificates"))); registerActionForCommand(coll->action(QStringLiteral("tools_refresh_openpgp_certificates"))); //--- registerActionForCommand(coll->action(QStringLiteral("crl_import_crl"))); //--- registerActionForCommand(coll->action(QStringLiteral("crl_clear_crl_cache"))); registerActionForCommand(coll->action(QStringLiteral("crl_dump_crl_cache"))); enableDisableActions(nullptr); } void KeyListController::registerAction(QAction *action, Command::Restrictions restrictions, Command * (*create)(QAbstractItemView *, KeyListController *)) { if (!action) { return; } Q_ASSERT(!action->isCheckable()); // can be added later, for now, disallow const Private::action_item ai = { action, restrictions, create }; - connect(action, SIGNAL(triggered()), this, SLOT(slotActionTriggered())); + connect(action, &QAction::triggered, this, [this, action]() { d->slotActionTriggered(action); }); d->actions.push_back(ai); } void KeyListController::registerCommand(Command *cmd) { if (!cmd || std::binary_search(d->commands.cbegin(), d->commands.cend(), cmd)) { return; } d->addCommand(cmd); qCDebug(KLEOPATRA_LOG) << (void *)cmd; if (d->commands.size() == 1) { Q_EMIT commandsExecuting(true); } } bool KeyListController::hasRunningCommands() const { return !d->commands.empty(); } bool KeyListController::shutdownWarningRequired() const { return std::any_of(d->commands.cbegin(), d->commands.cend(), std::mem_fn(&Command::warnWhenRunningAtShutdown)); } // slot void KeyListController::cancelCommands() { std::for_each(d->commands.begin(), d->commands.end(), std::mem_fn(&Command::cancel)); } void KeyListController::Private::connectView(QAbstractItemView *view) { connect(view, &QObject::destroyed, q, [this](QObject *obj) { slotDestroyed(obj); }); connect(view, &QAbstractItemView::doubleClicked, q, [this](const QModelIndex &index) { slotDoubleClicked(index); }); connect(view, &QAbstractItemView::activated, q, [this](const QModelIndex &index) { slotActivated(index); }); connect(view->selectionModel(), &QItemSelectionModel::selectionChanged, q, [this](const QItemSelection &oldSel, const QItemSelection &newSel) { slotSelectionChanged(oldSel, newSel); }); view->setContextMenuPolicy(Qt::CustomContextMenu); connect(view, &QWidget::customContextMenuRequested, q, [this](const QPoint &pos) { slotContextMenu(pos); }); } void KeyListController::Private::connectCommand(Command *cmd) { if (!cmd) { return; } connect(cmd, &QObject::destroyed, q, [this](QObject *obj) { slotDestroyed(obj); }); connect(cmd, &Command::finished, q, [this] { slotCommandFinished(); }); //connect( cmd, SIGNAL(canceled()), q, SLOT(slotCommandCanceled()) ); connect(cmd, &Command::info, q, &KeyListController::message); connect(cmd, &Command::progress, q, [this](const QString &message, int current, int total) { slotProgress(message, current, total); }); } void KeyListController::Private::slotDoubleClicked(const QModelIndex &idx) { QAbstractItemView *const view = qobject_cast(q->sender()); if (!view || !std::binary_search(views.cbegin(), views.cend(), view)) { return; } DetailsCommand *const c = new DetailsCommand(view, q); if (parentWidget) { c->setParentWidget(parentWidget); } c->setIndex(idx); c->start(); } void KeyListController::Private::slotActivated(const QModelIndex &idx) { Q_UNUSED(idx) QAbstractItemView *const view = qobject_cast(q->sender()); if (!view || !std::binary_search(views.cbegin(), views.cend(), view)) { return; } } void KeyListController::Private::slotSelectionChanged(const QItemSelection &old, const QItemSelection &new_) { Q_UNUSED(old) Q_UNUSED(new_) const QItemSelectionModel *const sm = qobject_cast(q->sender()); if (!sm) { return; } q->enableDisableActions(sm); } void KeyListController::Private::slotContextMenu(const QPoint &p) { QAbstractItemView *const view = qobject_cast(q->sender()); if (view && std::binary_search(views.cbegin(), views.cend(), view)) { Q_EMIT q->contextMenuRequested(view, view->viewport()->mapToGlobal(p)); } else { qCDebug(KLEOPATRA_LOG) << "sender is not a QAbstractItemView*!"; } } void KeyListController::Private::slotCommandFinished() { Command *const cmd = qobject_cast(q->sender()); if (!cmd || !std::binary_search(commands.cbegin(), commands.cend(), cmd)) { return; } qCDebug(KLEOPATRA_LOG) << (void *)cmd; if (commands.size() == 1) { Q_EMIT q->commandsExecuting(false); } } void KeyListController::enableDisableActions(const QItemSelectionModel *sm) const { const Command::Restrictions restrictionsMask = d->calculateRestrictionsMask(sm); for (const Private::action_item &ai : std::as_const(d->actions)) if (ai.action) { ai.action->setEnabled(ai.restrictions == (ai.restrictions & restrictionsMask)); } } static bool all_secret_are_not_owner_trust_ultimate(const std::vector &keys) { for (const Key &key : keys) if (key.hasSecret() && key.ownerTrust() == Key::Ultimate) { return false; } return true; } Command::Restrictions find_root_restrictions(const std::vector &keys) { bool trusted = false, untrusted = false; for (const Key &key : keys) if (key.isRoot()) if (key.userID(0).validity() == UserID::Ultimate) { trusted = true; } else { untrusted = true; } else { return Command::NoRestriction; } if (trusted) if (untrusted) { return Command::NoRestriction; } else { return Command::MustBeTrustedRoot; } else if (untrusted) { return Command::MustBeUntrustedRoot; } else { return Command::NoRestriction; } } Command::Restrictions KeyListController::Private::calculateRestrictionsMask(const QItemSelectionModel *sm) { if (!sm) { return Command::NoRestriction; } const KeyListModelInterface *const m = dynamic_cast(sm->model()); if (!m) { return Command::NoRestriction; } const std::vector keys = m->keys(sm->selectedRows()); if (keys.empty()) { return Command::NoRestriction; } Command::Restrictions result = Command::NeedSelection; if (keys.size() == 1) { result |= Command::OnlyOneKey; } #if GPGME_VERSION_NUMBER >= 0x011102 // 1.17.2 // we need to check the primary subkey because Key::hasSecret() is also true if just the secret key stub of an offline key is available const auto primaryKeyCanBeUsedForSecretKeyOperations = [](const auto &k) { return k.subkey(0).isSecret(); }; #else // older versions of GpgME did not always set the secret flag for card keys const auto primaryKeyCanBeUsedForSecretKeyOperations = [](const auto &k) { return k.subkey(0).isSecret() || k.subkey(0).isCardKey(); }; #endif if (std::all_of(keys.cbegin(), keys.cend(), primaryKeyCanBeUsedForSecretKeyOperations)) { result |= Command::NeedSecretKey; } if (std::all_of(std::begin(keys), std::end(keys), [](const auto &k) { return k.subkey(0).isSecret() && !k.subkey(0).isCardKey(); })) { result |= Command::NeedSecretKeyData; } if (std::all_of(keys.cbegin(), keys.cend(), [](const Key &key) { return key.protocol() == OpenPGP; })) { result |= Command::MustBeOpenPGP; } else if (std::all_of(keys.cbegin(), keys.cend(), [](const Key &key) { return key.protocol() == CMS; })) { result |= Command::MustBeCMS; } if (all_secret_are_not_owner_trust_ultimate(keys)) { result |= Command::MayOnlyBeSecretKeyIfOwnerTrustIsNotYetUltimate; } result |= find_root_restrictions(keys); if (const ReaderStatus *rs = ReaderStatus::instance()) { if (!rs->firstCardWithNullPin().empty()) { result |= Command::AnyCardHasNullPin; } if (rs->anyCardCanLearnKeys()) { result |= Command::AnyCardCanLearnKeys; } } return result; } -void KeyListController::Private::slotActionTriggered() -{ - if (const QObject *const s = q->sender()) { - const auto it = std::find_if(actions.cbegin(), actions.cend(), - [this](const action_item &item) { return item.action == q->sender(); }); - if (it != actions.end()) - if (Command *const c = it->createCommand(this->currentView, q)) { - if (parentWidget) { - c->setParentWidget(parentWidget); - } - c->start(); - } else - qCDebug(KLEOPATRA_LOG) << "createCommand() == NULL for action(?) \"" - << qPrintable(s->objectName()) << "\""; - else { - qCDebug(KLEOPATRA_LOG) << "I don't know anything about action(?) \"%s\"", qPrintable(s->objectName()); - } - } else { - qCDebug(KLEOPATRA_LOG) << "not called through a signal/slot connection (sender() == NULL)"; +void KeyListController::Private::slotActionTriggered(QAction *sender) +{ + const auto it = std::find_if(actions.cbegin(), actions.cend(), + [sender](const action_item &item) { return item.action == sender; }); + if (it != actions.end()) + if (Command *const c = it->createCommand(this->currentView, q)) { + if (parentWidget) { + c->setParentWidget(parentWidget); + } + c->start(); + } else + qCDebug(KLEOPATRA_LOG) << "createCommand() == NULL for action(?) \"" + << qPrintable(sender->objectName()) << "\""; + else { + qCDebug(KLEOPATRA_LOG) << "I don't know anything about action(?) \"%s\"", qPrintable(sender->objectName()); } } int KeyListController::Private::toolTipOptions() const { using namespace Kleo::Formatting; static const int validityFlags = Validity | Issuer | ExpiryDates | CertificateUsage; static const int ownerFlags = Subject | UserIDs | OwnerTrust; static const int detailsFlags = StorageLocation | CertificateType | SerialNumber | Fingerprint; const TooltipPreferences prefs; int flags = KeyID; flags |= prefs.showValidity() ? validityFlags : 0; flags |= prefs.showOwnerInformation() ? ownerFlags : 0; flags |= prefs.showCertificateDetails() ? detailsFlags : 0; return flags; } void KeyListController::updateConfig() { const int opts = d->toolTipOptions(); if (d->flatModel) { d->flatModel->setToolTipOptions(opts); } if (d->hierarchicalModel) { d->hierarchicalModel->setToolTipOptions(opts); } } #include "moc_keylistcontroller.cpp" diff --git a/src/view/keylistcontroller.h b/src/view/keylistcontroller.h index e9bbbcd89..50b3c2e26 100644 --- a/src/view/keylistcontroller.h +++ b/src/view/keylistcontroller.h @@ -1,104 +1,101 @@ /* -*- mode: c++; c-basic-offset:4 -*- controllers/keylistcontroller.h This file is part of Kleopatra, the KDE keymanager SPDX-FileCopyrightText: 2007 Klarälvdalens Datakonsult AB SPDX-License-Identifier: GPL-2.0-or-later */ #pragma once #include #include #include #include class QAbstractItemView; class QAction; class QPoint; class QItemSelectionModel; class KActionCollection; namespace Kleo { class AbstractKeyListModel; class Command; class TabWidget; class KeyListController : public QObject { Q_OBJECT public: explicit KeyListController(QObject *parent = nullptr); ~KeyListController() override; std::vector views() const; void setFlatModel(AbstractKeyListModel *model); AbstractKeyListModel *flatModel() const; void setHierarchicalModel(AbstractKeyListModel *model); AbstractKeyListModel *hierarchicalModel() const; void setParentWidget(QWidget *parent); QWidget *parentWidget() const; QAbstractItemView *currentView() const; void setTabWidget(TabWidget *tabs); TabWidget *tabWidget() const; void registerCommand(Command *cmd); void createActions(KActionCollection *collection); template void registerActionForCommand(QAction *action) { this->registerAction(action, T_Command::restrictions(), &KeyListController::template create); } void enableDisableActions(const QItemSelectionModel *sm) const; bool hasRunningCommands() const; bool shutdownWarningRequired() const; private: void registerAction(QAction *action, Command::Restrictions restrictions, Command * (*create)(QAbstractItemView *, KeyListController *)); template static Command *create(QAbstractItemView *v, KeyListController *c) { return new T_Command(v, c); } public Q_SLOTS: void addView(QAbstractItemView *view); void removeView(QAbstractItemView *view); void setCurrentView(QAbstractItemView *view); void cancelCommands(); void updateConfig(); Q_SIGNALS: void progress(int current, int total); void message(const QString &msg, int timeout = 0); void commandsExecuting(bool); void contextMenuRequested(QAbstractItemView *view, const QPoint &p); private: class Private; kdtools::pimpl_ptr d; - - Q_PRIVATE_SLOT(d, void slotActionTriggered()) - Q_PRIVATE_SLOT(d, void slotCurrentViewChanged(QAbstractItemView *)) }; } diff --git a/src/view/keytreeview.cpp b/src/view/keytreeview.cpp index e982d2c03..fd7989de8 100644 --- a/src/view/keytreeview.cpp +++ b/src/view/keytreeview.cpp @@ -1,809 +1,798 @@ /* -*- mode: c++; c-basic-offset:4 -*- view/keytreeview.cpp This file is part of Kleopatra, the KDE keymanager SPDX-FileCopyrightText: 2009 Klarälvdalens Datakonsult AB SPDX-License-Identifier: GPL-2.0-or-later */ #include #include "keytreeview.h" +#include "searchbar.h" #include #include #include #include #include #include "utils/headerview.h" #include "utils/tags.h" #include #include #include #include #include "kleopatra_debug.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #define TAGS_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) { auto 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 == TAGS_COLUMN) && action->isChecked()) { Tags::enableTags(); } if (action->isChecked()) { showColumn(col); } else { hideColumn(col); } auto tv = qobject_cast (parent()); if (tv) { tv->resizeColumns(); } }); } for (QAction *action : std::as_const(mColumnActions)) { const int column = action->data().toInt(); action->setChecked(!isColumnHidden(column)); } mHeaderPopup->popup(mapToGlobal(e->pos())); return true; } return false; } void keyPressEvent(QKeyEvent *event) override; QModelIndex moveCursor(CursorAction cursorAction, Qt::KeyboardModifiers modifiers) override; private: bool mMoveCursorUpdatedView = false; QMenu *mHeaderPopup = nullptr; QList mColumnActions; }; void TreeView::keyPressEvent(QKeyEvent *event) { mMoveCursorUpdatedView = false; QTreeView::keyPressEvent(event); if (mMoveCursorUpdatedView) { event->accept(); } } QModelIndex TreeView::moveCursor(CursorAction cursorAction, Qt::KeyboardModifiers modifiers) { // this code is based heavily on QTreeView::moveCursor() QModelIndex current = currentIndex(); if (!current.isValid()) { // let QTreeView handle invalid current index return QTreeView::moveCursor(cursorAction, modifiers); } if (isRightToLeft()) { if (cursorAction == MoveRight) { cursorAction = MoveLeft; } else if (cursorAction == MoveLeft) { cursorAction = MoveRight; } } switch (cursorAction) { case MoveLeft: { // HACK: call QTreeView::moveCursor with invalid cursor action to make it call the private executePostedLayout() (void) QTreeView::moveCursor(static_cast(-1), modifiers); int visualColumn = header()->visualIndex(current.column()) - 1; while (visualColumn >= 0 && isColumnHidden(header()->logicalIndex(visualColumn))) { visualColumn--; } int newColumn = header()->logicalIndex(visualColumn); QModelIndex next = current.sibling(current.row(), newColumn); if (next.isValid()) { return next; } //last restort: we change the scrollbar value QScrollBar *sb = horizontalScrollBar(); int oldValue = sb->value(); sb->setValue(sb->value() - sb->singleStep()); if (oldValue != sb->value()) { mMoveCursorUpdatedView = true; } updateGeometries(); viewport()->update(); break; } case MoveRight: { // HACK: call QTreeView::moveCursor with invalid cursor action to make it call the private executePostedLayout() (void) QTreeView::moveCursor(static_cast(-1), modifiers); int visualColumn = header()->visualIndex(current.column()) + 1; while (visualColumn < model()->columnCount(current.parent()) && isColumnHidden(header()->logicalIndex(visualColumn))) { visualColumn++; } const int newColumn = header()->logicalIndex(visualColumn); const QModelIndex next = current.sibling(current.row(), newColumn); if (next.isValid()) { return next; } //last restort: we change the scrollbar value QScrollBar *sb = horizontalScrollBar(); int oldValue = sb->value(); sb->setValue(sb->value() + sb->singleStep()); if (oldValue != sb->value()) { mMoveCursorUpdatedView = true; } updateGeometries(); viewport()->update(); break; } default: return QTreeView::moveCursor(cursorAction, modifiers); } return current; } const KeyListModelInterface * keyListModel(const QTreeView &view) { const KeyListModelInterface *const klmi = dynamic_cast(view.model()); Q_ASSERT(klmi); return klmi; } } // 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 (auto 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 (auto const hv = static_cast(m_view->header())) { return hv->sectionSizes(); } else { return std::vector(); } } void KeyTreeView::init() { KDAB_SET_OBJECT_NAME(m_proxy); KDAB_SET_OBJECT_NAME(m_view); if (m_group.isValid()) { // Reopen as non const KConfig *conf = m_group.config(); m_group = conf->group(m_group.name()); } if (m_additionalProxy && m_additionalProxy->objectName().isEmpty()) { KDAB_SET_OBJECT_NAME(m_additionalProxy); } QLayout *layout = new QVBoxLayout(this); KDAB_SET_OBJECT_NAME(layout); layout->setContentsMargins(0, 0, 0, 0); layout->addWidget(m_view); auto headerView = new HeaderView(Qt::Horizontal); KDAB_SET_OBJECT_NAME(headerView); headerView->installEventFilter(m_view); headerView->setSectionsMovable(true); m_view->setHeader(headerView); m_view->setSelectionBehavior(QAbstractItemView::SelectRows); m_view->setSelectionMode(QAbstractItemView::ExtendedSelection); m_view->setAllColumnsShowFocus(false); m_view->setSortingEnabled(true); m_view->setAccessibleName(i18n("Certificates")); m_view->setAccessibleDescription(m_isHierarchical ? i18n("Hierarchical list of certificates") : i18n("List of certificates")); 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); auto rearangingModel = new KeyRearrangeColumnsProxyModel(this); rearangingModel->setSourceModel(m_proxy); rearangingModel->setSourceColumns(QVector() << KeyList::PrettyName << KeyList::PrettyEMail << KeyList::Validity << KeyList::ValidFrom << KeyList::ValidUntil << KeyList::TechnicalDetails << KeyList::KeyID << KeyList::Fingerprint << KeyList::OwnerTrust << KeyList::Origin << KeyList::LastUpdate << KeyList::Issuer << KeyList::SerialNumber // If a column is added before this TAGS_COLUMN define has to be updated accordingly << KeyList::Remarks ); m_view->setModel(rearangingModel); /* Handle expansion state */ if (m_group.isValid()) { 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(KeyList::KeyRole).value(); if (key.isNull()) { return; } const auto fpr = QString::fromLatin1(key.primaryFingerprint()); if (m_expandedKeys.contains(fpr)) { return; } m_expandedKeys << fpr; if (m_group.isValid()) { 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(KeyList::KeyRole).value(); if (key.isNull()) { return; } m_expandedKeys.removeAll(QString::fromLatin1(key.primaryFingerprint())); if (m_group.isValid()) { 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(); setUpTagKeys(); if (!m_onceResized) { m_onceResized = true; resizeColumns(); } }); }); resizeColumns(); if (m_group.isValid()) { restoreLayout(m_group); } } void KeyTreeView::restoreExpandState() { if (!KeyCache::instance()->initialized()) { qCWarning(KLEOPATRA_LOG) << "Restore expand state before keycache available. Aborting."; return; } for (const auto &fpr: std::as_const(m_expandedKeys)) { const KeyListModelInterface *const km = keyListModel(*m_view); if (!km) { qCWarning(KLEOPATRA_LOG) << "invalid model"; return; } const auto key = KeyCache::instance()->findByFingerprint(fpr.toLatin1().constData()); if (key.isNull()) { qCDebug(KLEOPATRA_LOG) << "Cannot find:" << fpr << "anymore in cache"; m_expandedKeys.removeAll(fpr); return; } const auto idx = km->index(key); if (!idx.isValid()) { qCDebug(KLEOPATRA_LOG) << "Cannot find:" << fpr << "anymore in model"; m_expandedKeys.removeAll(fpr); return; } m_view->expand(idx); } } void KeyTreeView::setUpTagKeys() { const auto tagKeys = Tags::tagKeys(); if (m_hierarchicalModel) { m_hierarchicalModel->setRemarkKeys(tagKeys); } if (m_flatModel) { m_flatModel->setRemarkKeys(tagKeys); } } void KeyTreeView::saveLayout(KConfigGroup &group) { 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)); } group.writeEntry("ColumnVisibility", columnVisibility); group.writeEntry("ColumnOrder", columnOrder); group.writeEntry("ColumnWidths", columnWidths); group.writeEntry("SortAscending", (int)header->sortIndicatorOrder()); if (header->isSortIndicatorShown()) { group.writeEntry("SortColumn", header->sortIndicatorSection()); } else { group.writeEntry("SortColumn", -1); } } void KeyTreeView::restoreLayout(const KConfigGroup &group) { QHeaderView *header = m_view->header(); QVariantList columnVisibility = group.readEntry("ColumnVisibility", QVariantList()); QVariantList columnOrder = group.readEntry("ColumnOrder", QVariantList()); QVariantList columnWidths = 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 == TAGS_COLUMN) && visible) { Tags::enableTags(); } if (!visible) { m_view->hideColumn(i); } } m_onceResized = true; } int sortOrder = group.readEntry("SortAscending", (int)Qt::AscendingOrder); int sortColumn = group.readEntry("SortColumn", 0); if (sortColumn >= 0) { m_view->sortByColumn(sortColumn, (Qt::SortOrder)sortOrder); } } KeyTreeView::~KeyTreeView() { if (m_group.isValid()) { saveLayout(m_group); } } static QAbstractProxyModel *find_last_proxy(QAbstractProxyModel *pm) { Q_ASSERT(pm); while (auto 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); } namespace { QItemSelection itemSelectionFromKeys(const std::vector &keys, const QTreeView &view) { const QModelIndexList indexes = keyListModel(view)->indexes(keys); return std::accumulate( indexes.cbegin(), indexes.cend(), QItemSelection(), [] (QItemSelection &selection, const QModelIndex &index) { if (index.isValid()) { selection.merge(QItemSelection(index, index), QItemSelectionModel::Select); } return selection; }); } } void KeyTreeView::selectKeys(const std::vector &keys) { m_view->selectionModel()->select(itemSelectionFromKeys(keys, *m_view), QItemSelectionModel::ClearAndSelect | QItemSelectionModel::Rows); } std::vector KeyTreeView::selectedKeys() const { return keyListModel(*m_view)->keys(m_view->selectionModel()->selectedRows()); } void KeyTreeView::setHierarchicalView(bool on) { if (on == m_isHierarchical) { return; } if (on && !hierarchicalModel()) { qCWarning(KLEOPATRA_LOG) << "hierarchical view requested, but no hierarchical model set"; return; } if (!on && !flatModel()) { qCWarning(KLEOPATRA_LOG) << "flat view requested, but no flat model set"; return; } const std::vector selectedKeys = this->selectedKeys(); const Key currentKey = keyListModel(*m_view)->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 = keyListModel(*m_view)->index(currentKey); if (currentIndex.isValid()) { m_view->selectionModel()->setCurrentIndex(currentIndex, QItemSelectionModel::NoUpdate); m_view->scrollTo(currentIndex); } } m_view->setAccessibleDescription(m_isHierarchical ? i18n("Hierarchical list of certificates") : i18n("List of certificates")); 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) +void KeyTreeView::disconnectSearchBar() { - 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); + for (const auto &connection : m_connections) { + disconnect(connection); } + m_connections.clear(); } -bool KeyTreeView::connectSearchBar(const QObject *bar) +bool KeyTreeView::connectSearchBar(const SearchBar *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; + m_connections.reserve(4); + m_connections.push_back(connect(this, &KeyTreeView::stringFilterChanged, bar, &SearchBar::setStringFilter)); + m_connections.push_back(connect(bar, &SearchBar::stringFilterChanged, this, &KeyTreeView::setStringFilter)); + m_connections.push_back(connect(this, &KeyTreeView::keyFilterChanged, bar, &SearchBar::setKeyFilter)); + m_connections.push_back(connect(bar, &SearchBar::keyFilterChanged, this, &KeyTreeView::setKeyFilter)); + + return std::all_of(m_connections.cbegin(), m_connections.cend(), [](const QMetaObject::Connection &conn) { + return conn; + }); } void KeyTreeView::resizeColumns() { m_view->setColumnWidth(KeyList::PrettyName, 260); m_view->setColumnWidth(KeyList::PrettyEMail, 260); for (int i = 2; i < m_view->model()->columnCount(); ++i) { m_view->resizeColumnToContents(i); } } diff --git a/src/view/keytreeview.h b/src/view/keytreeview.h index f632cf6f0..fe356e93d 100644 --- a/src/view/keytreeview.h +++ b/src/view/keytreeview.h @@ -1,158 +1,161 @@ /* -*- mode: c++; c-basic-offset:4 -*- view/keytreeview.h This file is part of Kleopatra, the KDE keymanager SPDX-FileCopyrightText: 2009 Klarälvdalens Datakonsult AB SPDX-License-Identifier: GPL-2.0-or-later */ #pragma once #include #include #include #include #include #include #include class QTreeView; namespace Kleo { class KeyFilter; class AbstractKeyListModel; class AbstractKeyListSortFilterProxyModel; class KeyListSortFilterProxyModel; +class SearchBar; class 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); ~KeyTreeView() override; 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 disconnectSearchBar(); + bool connectSearchBar(const SearchBar *bar); void resizeColumns(); void saveLayout(KConfigGroup &group); void restoreLayout(const KConfigGroup &group); 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 setUpTagKeys(); 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; + std::vector m_connections; + KConfigGroup m_group; bool m_isHierarchical : 1; bool m_onceResized : 1; }; }