diff --git a/src/commands/command.cpp b/src/commands/command.cpp index 2216b3761..108bfa3c9 100644 --- a/src/commands/command.cpp +++ b/src/commands/command.cpp @@ -1,303 +1,313 @@ /* -*- mode: c++; c-basic-offset:4 -*- commands/command.cpp This file is part of Kleopatra, the KDE keymanager SPDX-FileCopyrightText: 2007 Klarälvdalens Datakonsult AB SPDX-License-Identifier: GPL-2.0-or-later */ #include #include "command.h" #include "command_p.h" #include "signencryptfilescommand.h" #include "importcertificatefromfilecommand.h" #include "decryptverifyfilescommand.h" #include "detailscommand.h" #include "lookupcertificatescommand.h" #include "checksumverifyfilescommand.h" #include #include #include #include "kleopatra_debug.h" #include #include #include using namespace Kleo; using namespace Kleo::Commands; using namespace GpgME; Command::Private::Private(Command *qq) : q(qq), autoDelete(true), warnWhenRunningAtShutdown(true) { } Command::Private::Private(Command *qq, KeyListController *controller) : q(qq), autoDelete(true), warnWhenRunningAtShutdown(true), controller_(controller) { } Command::Private::Private(Command *qq, QWidget *parent) : q(qq), autoDelete(true), warnWhenRunningAtShutdown(true), parentWidget_(parent) { } Command::Private::~Private() { qCDebug(KLEOPATRA_LOG); } Command::Command(KeyListController *p) : QObject(p), d(new Private(this, p)) { if (p) { p->registerCommand(this); } } Command::Command(QAbstractItemView *v, KeyListController *p) : QObject(p), d(new Private(this, p)) { if (p) { p->registerCommand(this); } if (v) { setView(v); } } Command::Command(Private *pp) : QObject(pp->controller_), d(pp) { if (pp->controller_) { pp->controller_->registerCommand(this); } } Command::Command(QAbstractItemView *v, Private *pp) : QObject(pp->controller_), d(pp) { if (pp->controller_) { pp->controller_->registerCommand(this); } if (v) { setView(v); } } Command::Command(const GpgME::Key &key) : QObject(nullptr), d(new Private(this)) { d->keys_ = std::vector(1, key); } Command::Command(const std::vector &keys) : QObject(nullptr), d(new Private(this)) { d->keys_ = keys; } Command::Command(const Key &key, Private *pp) : QObject(nullptr), d(pp) { d->keys_ = std::vector(1, key); } Command::Command(const std::vector &keys, Private *pp) : QObject(nullptr), d(pp) { d->keys_ = keys; } Command::~Command() { qCDebug(KLEOPATRA_LOG); } void Command::setAutoDelete(bool on) { d->autoDelete = on; } bool Command::autoDelete() const { return d->autoDelete; } void Command::setWarnWhenRunningAtShutdown(bool on) { d->warnWhenRunningAtShutdown = on; } bool Command::warnWhenRunningAtShutdown() const { return d->warnWhenRunningAtShutdown; } void Command::setParentWidget(QWidget *widget) { d->parentWidget_ = widget; } void Command::setParentWId(WId wid) { d->parentWId = wid; } void Command::setView(QAbstractItemView *view) { if (view == d->view_) { return; } d->view_ = view; if (!view || !d->indexes_.empty()) { return; } const QItemSelectionModel *const sm = view->selectionModel(); if (!sm) { qCWarning(KLEOPATRA_LOG) << "view " << (void *)view << " has no selectionModel!"; return; } const QList selected = sm->selectedRows(); if (!selected.empty()) { std::copy(selected.begin(), selected.end(), std::back_inserter(d->indexes_)); return; } } void Command::setIndex(const QModelIndex &idx) { d->indexes_.clear(); d->indexes_.push_back(idx); } void Command::setIndexes(const QList &idx) { d->indexes_.clear(); std::copy(idx.begin(), idx.end(), std::back_inserter(d->indexes_)); } void Command::setKey(const Key &key) { d->keys_.clear(); if (!key.isNull()) { d->keys_.push_back(key); } } void Command::setKeys(const std::vector &keys) { d->keys_ = keys; } void Command::start() { doStart(); } void Command::cancel() { qCDebug(KLEOPATRA_LOG) << metaObject()->className(); doCancel(); Q_EMIT canceled(); } void Command::addTemporaryView(const QString &title, AbstractKeyListSortFilterProxyModel *proxy, const QString &tabToolTip) { if (TabWidget *const tw = d->controller_ ? d->controller_->tabWidget() : nullptr) if (QAbstractItemView *const v = tw->addTemporaryView(title, proxy, tabToolTip)) { setView(v); } } void Command::applyWindowID(QWidget *w) const { if (w) { if (d->parentWId) { if (QWidget *pw = QWidget::find(d->parentWId)) { + // remember the current focus widget; re-parenting resets it + QWidget *focusWidget = w->focusWidget(); w->setParent(pw, w->windowFlags()); + if (focusWidget) { + focusWidget->setFocus(); + } } else { w->setAttribute(Qt::WA_NativeWindow, true); KWindowSystem::setMainWindow(w->windowHandle(), d->parentWId); } } else { + // remember the current focus widget; re-parenting resets it + QWidget *focusWidget = w->focusWidget(); w->setParent(d->parentWidgetOrView(), w->windowFlags()); + if (focusWidget) { + focusWidget->setFocus(); + } } } } // static QVector Command::commandsForFiles(const QStringList &files) { QStringList importFiles, decryptFiles, encryptFiles, checksumFiles; QVector cmds; for (const QString &fileName : files) { const unsigned int classification = classify(fileName); if (classification & Class::AnyCertStoreType) { importFiles << fileName; } else if (classification & Class::AnyMessageType) { // For any message we decrypt / verify. This includes // the class CipherText decryptFiles << fileName; } else if (isChecksumFile(fileName)) { checksumFiles << fileName; } else { QFileInfo fi(fileName); if (fi.isReadable()) { encryptFiles << fileName; } } } if (!importFiles.isEmpty()) { cmds << new ImportCertificateFromFileCommand(importFiles, nullptr); } if (!decryptFiles.isEmpty()) { cmds << new DecryptVerifyFilesCommand(decryptFiles, nullptr); } if (!encryptFiles.isEmpty()) { cmds << new SignEncryptFilesCommand(encryptFiles, nullptr); } if (!checksumFiles.isEmpty()) { cmds << new ChecksumVerifyFilesCommand(checksumFiles, nullptr); } return cmds; } // static Command *Command::commandForQuery(const QString &query) { const auto cache = Kleo::KeyCache::instance(); GpgME::Key key = cache->findByKeyIDOrFingerprint(query.toLocal8Bit().data()); if (key.isNull() && query.size() > 16) { // Try to find by subkeyid std::vector id; id.push_back(query.right(16).toStdString()); auto keys = cache->findSubkeysByKeyID(id); if (keys.size()) { key = keys[0].parent(); } } if (key.isNull()) { return new LookupCertificatesCommand(query, nullptr); } else { return new DetailsCommand(key, nullptr); } } diff --git a/src/dialogs/editgroupdialog.cpp b/src/dialogs/editgroupdialog.cpp index 0665abead..f706405b5 100644 --- a/src/dialogs/editgroupdialog.cpp +++ b/src/dialogs/editgroupdialog.cpp @@ -1,311 +1,314 @@ /* dialogs/editgroupdialog.cpp This file is part of Kleopatra, the KDE keymanager SPDX-FileCopyrightText: 2021 g10 Code GmbH SPDX-FileContributor: Ingo Klöcker SPDX-License-Identifier: GPL-2.0-or-later */ #include "editgroupdialog.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "kleopatra_debug.h" using namespace Kleo; using namespace Kleo::Dialogs; using namespace GpgME; Q_DECLARE_METATYPE(GpgME::Key) namespace { class ProxyModel : public AbstractKeyListSortFilterProxyModel { Q_OBJECT public: ProxyModel(QObject *parent = nullptr) : AbstractKeyListSortFilterProxyModel(parent) { } ~ProxyModel() override = default; ProxyModel *clone() const override { // compiler-generated copy ctor is fine! return new ProxyModel(*this); } }; } class EditGroupDialog::Private { friend class ::Kleo::Dialogs::EditGroupDialog; EditGroupDialog *const q; struct { QLineEdit *groupNameEdit = nullptr; QLineEdit *availableKeysFilter = nullptr; QListView *availableKeysList = nullptr; QLineEdit *groupKeysFilter = nullptr; QListView *groupKeysList = nullptr; QDialogButtonBox *buttonBox = nullptr; } ui; AbstractKeyListModel *availableKeysModel = nullptr; ProxyModel *availableKeysFilterModel = nullptr; AbstractKeyListModel *groupKeysModel = nullptr; ProxyModel *groupKeysFilterModel = nullptr; public: Private(EditGroupDialog *qq) : q(qq) { auto mainLayout = new QVBoxLayout(q); auto groupNameLayout = new QHBoxLayout(); groupNameLayout->addWidget(new QLabel(i18nc("Name of a group of keys", "Name:"))); ui.groupNameEdit = new QLineEdit(); groupNameLayout->addWidget(ui.groupNameEdit); mainLayout->addLayout(groupNameLayout); mainLayout->addWidget(new KSeparator(Qt::Horizontal)); auto centerLayout = new QHBoxLayout(); auto availableKeysLayout = new QVBoxLayout(); availableKeysLayout->addWidget(new QLabel(i18n("Available keys:"))); ui.availableKeysFilter = new QLineEdit(); ui.availableKeysFilter->setClearButtonEnabled(true); ui.availableKeysFilter->setPlaceholderText(i18nc("Placeholder text", "Search...")); availableKeysLayout->addWidget(ui.availableKeysFilter); availableKeysModel = AbstractKeyListModel::createFlatKeyListModel(q); availableKeysModel->useKeyCache(true, KeyList::AllKeys); availableKeysFilterModel = new ProxyModel(q); availableKeysFilterModel->setFilterCaseSensitivity(Qt::CaseInsensitive); availableKeysFilterModel->setFilterKeyColumn(KeyList::Summary); availableKeysFilterModel->setSortCaseSensitivity(Qt::CaseInsensitive); availableKeysFilterModel->setSourceModel(availableKeysModel); availableKeysFilterModel->sort(KeyList::Summary, Qt::AscendingOrder); ui.availableKeysList = new QListView(); ui.availableKeysList->setModel(availableKeysFilterModel); ui.availableKeysList->setModelColumn(KeyList::Summary); ui.availableKeysList->setSelectionBehavior(QAbstractItemView::SelectRows); ui.availableKeysList->setSelectionMode(QAbstractItemView::ExtendedSelection); availableKeysLayout->addWidget(ui.availableKeysList, /*stretch=*/ 1); centerLayout->addLayout(availableKeysLayout, /*stretch=*/ 1); auto buttonsLayout = new QVBoxLayout(); buttonsLayout->addStretch(1); auto addButton = new QPushButton(); addButton->setIcon(QIcon::fromTheme(QStringLiteral("arrow-right"))); addButton->setToolTip(i18n("Add the selected keys to the group")); addButton->setEnabled(false); buttonsLayout->addWidget(addButton); auto removeButton = new QPushButton(); removeButton->setIcon(QIcon::fromTheme(QStringLiteral("arrow-left"))); removeButton->setToolTip(i18n("Remove the selected keys from the group")); removeButton->setEnabled(false); buttonsLayout->addWidget(removeButton); buttonsLayout->addStretch(1); centerLayout->addLayout(buttonsLayout); auto groupKeysLayout = new QVBoxLayout(); groupKeysLayout->addWidget(new QLabel(i18n("Group keys:"))); ui.groupKeysFilter = new QLineEdit(); ui.groupKeysFilter->setClearButtonEnabled(true); ui.groupKeysFilter->setPlaceholderText(i18nc("Placeholder text", "Search...")); groupKeysLayout->addWidget(ui.groupKeysFilter); groupKeysModel = AbstractKeyListModel::createFlatKeyListModel(q); groupKeysFilterModel = new ProxyModel(q); groupKeysFilterModel->setFilterCaseSensitivity(Qt::CaseInsensitive); groupKeysFilterModel->setFilterKeyColumn(KeyList::Summary); groupKeysFilterModel->setSortCaseSensitivity(Qt::CaseInsensitive); groupKeysFilterModel->setSourceModel(groupKeysModel); groupKeysFilterModel->sort(KeyList::Summary, Qt::AscendingOrder); ui.groupKeysList = new QListView(); ui.groupKeysList->setModel(groupKeysFilterModel); ui.groupKeysList->setModelColumn(KeyList::Summary); ui.groupKeysList->setSelectionBehavior(QAbstractItemView::SelectRows); ui.groupKeysList->setSelectionMode(QAbstractItemView::ExtendedSelection); groupKeysLayout->addWidget(ui.groupKeysList, /*stretch=*/ 1); centerLayout->addLayout(groupKeysLayout, /*stretch=*/ 1); mainLayout->addLayout(centerLayout); mainLayout->addWidget(new KSeparator(Qt::Horizontal)); ui.buttonBox = new QDialogButtonBox(QDialogButtonBox::Ok | QDialogButtonBox::Cancel); QPushButton *okButton = ui.buttonBox->button(QDialogButtonBox::Ok); KGuiItem::assign(okButton, KStandardGuiItem::ok()); KGuiItem::assign(ui.buttonBox->button(QDialogButtonBox::Cancel), KStandardGuiItem::cancel()); okButton->setEnabled(false); mainLayout->addWidget(ui.buttonBox); connect(ui.groupNameEdit, &QLineEdit::textChanged, q, [okButton] (const QString &text) { okButton->setEnabled(!text.trimmed().isEmpty()); }); connect(ui.availableKeysFilter, &QLineEdit::textChanged, availableKeysFilterModel, &QSortFilterProxyModel::setFilterFixedString); connect(ui.availableKeysList->selectionModel(), &QItemSelectionModel::selectionChanged, q, [addButton] (const QItemSelection &selected, const QItemSelection &) { addButton->setEnabled(!selected.isEmpty()); }); connect(ui.groupKeysFilter, &QLineEdit::textChanged, groupKeysFilterModel, &QSortFilterProxyModel::setFilterFixedString); connect(ui.groupKeysList->selectionModel(), &QItemSelectionModel::selectionChanged, q, [removeButton] (const QItemSelection &selected, const QItemSelection &) { removeButton->setEnabled(!selected.isEmpty()); }); connect(addButton, &QPushButton::clicked, q, [this] () { addKeysToGroup(); }); connect(removeButton, &QPushButton::clicked, q, [this] () { removeKeysFromGroup(); }); connect(ui.buttonBox, &QDialogButtonBox::accepted, q, &EditGroupDialog::accept); connect(ui.buttonBox, &QDialogButtonBox::rejected, q, &EditGroupDialog::reject); + // give initial keyboard focus to the keys filter + ui.availableKeysFilter->setFocus(); + // calculate default size with enough space for the key list const auto fm = q->fontMetrics(); const QSize sizeHint = q->sizeHint(); const QSize defaultSize = QSize(qMax(sizeHint.width(), 150 * fm.horizontalAdvance(QLatin1Char('x'))), sizeHint.height() + 12 * fm.lineSpacing()); restoreLayout(defaultSize); } ~Private() { saveLayout(); } private: void saveLayout() { KConfigGroup configGroup(KSharedConfig::openConfig(), "EditGroupDialog"); configGroup.writeEntry("Size", q->size()); configGroup.sync(); } void restoreLayout(const QSize &defaultSize) { const KConfigGroup configGroup(KSharedConfig::openConfig(), "EditGroupDialog"); const QSize size = configGroup.readEntry("Size", defaultSize); if (size.isValid()) { q->resize(size); } } void addKeysToGroup(); void removeKeysFromGroup(); }; namespace { std::vector getSelectedKeys(const QListView *view) { const QModelIndexList selectedRows = view->selectionModel()->selectedRows(); if (selectedRows.isEmpty()) { return std::vector(); } const KeyListModelInterface *const keyListModel = dynamic_cast(view->model()); Q_ASSERT(keyListModel); return keyListModel->keys(selectedRows); } void setSelectedKeys(const QListView *view, const std::vector &keys) { const KeyListModelInterface *const keyListModel = dynamic_cast(view->model()); Q_ASSERT(keyListModel); const QModelIndexList indexes = keyListModel->indexes(keys); for (const QModelIndex &idx : indexes) { if (idx.isValid()) { view->selectionModel()->select(idx, QItemSelectionModel::Select | QItemSelectionModel::Rows); } } } } void EditGroupDialog::Private::addKeysToGroup() { const std::vector selectedGroupKeys = getSelectedKeys(ui.groupKeysList); const std::vector selectedKeys = getSelectedKeys(ui.availableKeysList); groupKeysModel->addKeys(selectedKeys); setSelectedKeys(ui.groupKeysList, selectedGroupKeys); } void EditGroupDialog::Private::removeKeysFromGroup() { const std::vector selectedKeys = getSelectedKeys(ui.groupKeysList); for (const Key &key : selectedKeys) { groupKeysModel->removeKey(key); } } EditGroupDialog::EditGroupDialog(QWidget *parent) : QDialog(parent) , d(new Private(this)) { setWindowTitle(i18nc("@title:window", "Edit Group")); } EditGroupDialog::~EditGroupDialog() { } void EditGroupDialog::setGroupName(const QString &name) { d->ui.groupNameEdit->setText(name); } QString EditGroupDialog::groupName() const { return d->ui.groupNameEdit->text().trimmed(); } void EditGroupDialog::setGroupKeys(const std::vector &keys) { d->groupKeysModel->setKeys(keys); } std::vector EditGroupDialog::groupKeys() const { std::vector keys; keys.reserve(d->groupKeysModel->rowCount()); for (int row = 0; row < d->groupKeysModel->rowCount(); ++row) { const QModelIndex index = d->groupKeysModel->index(row, 0); keys.push_back(d->groupKeysModel->key(index)); } return keys; } #include "editgroupdialog.moc"