File indexing completed on 2024-05-12 05:20:02

0001 /* -*- mode: c++; c-basic-offset:4 -*-
0002     mainwindow.cpp
0003 
0004     This file is part of Kleopatra, the KDE keymanager
0005     SPDX-FileCopyrightText: 2007 Klarälvdalens Datakonsult AB
0006 
0007     SPDX-License-Identifier: GPL-2.0-or-later
0008 */
0009 
0010 #include <config-kleopatra.h>
0011 
0012 #include "aboutdata.h"
0013 #include "kleopatraapplication.h"
0014 #include "mainwindow.h"
0015 #include "settings.h"
0016 
0017 #include <interfaces/focusfirstchild.h>
0018 
0019 #include "view/keycacheoverlay.h"
0020 #include "view/keylistcontroller.h"
0021 #include "view/padwidget.h"
0022 #include "view/searchbar.h"
0023 #include "view/smartcardwidget.h"
0024 #include "view/tabwidget.h"
0025 #include "view/welcomewidget.h"
0026 
0027 #include "commands/decryptverifyfilescommand.h"
0028 #include "commands/importcertificatefromfilecommand.h"
0029 #include "commands/importcrlcommand.h"
0030 #include "commands/selftestcommand.h"
0031 #include "commands/signencryptfilescommand.h"
0032 
0033 #include "utils/action_data.h"
0034 #include "utils/clipboardmenu.h"
0035 #include "utils/detail_p.h"
0036 #include "utils/filedialog.h"
0037 #include "utils/gui-helper.h"
0038 #include "utils/keyexportdraghandler.h"
0039 
0040 #include <Libkleo/GnuPG>
0041 
0042 #include "dialogs/updatenotification.h"
0043 
0044 // needed for GPGME_VERSION_NUMBER
0045 #include <gpgme.h>
0046 
0047 #include "kleopatra_debug.h"
0048 #include <KAboutData>
0049 #include <KActionCollection>
0050 #include <KActionMenu>
0051 #include <KColorScheme>
0052 #include <KConfigDialog>
0053 #include <KConfigGroup>
0054 #include <KEditToolBar>
0055 #include <KLocalizedString>
0056 #include <KMessageBox>
0057 #include <KShortcutsDialog>
0058 #include <KStandardAction>
0059 #include <KStandardGuiItem>
0060 #include <KToolBar>
0061 #include <KXMLGUIFactory>
0062 #include <QAction>
0063 #include <QApplication>
0064 #include <QLineEdit>
0065 #include <QSize>
0066 
0067 #include <QAbstractItemView>
0068 #include <QCloseEvent>
0069 #include <QDesktopServices>
0070 #include <QDir>
0071 #include <QLabel>
0072 #include <QMenu>
0073 #include <QMimeData>
0074 #include <QPixmap>
0075 #include <QProcess>
0076 #include <QSettings>
0077 #include <QStackedWidget>
0078 #include <QStatusBar>
0079 #include <QTimer>
0080 #include <QVBoxLayout>
0081 
0082 #include <Libkleo/Classify>
0083 #include <Libkleo/Compliance>
0084 #include <Libkleo/DocAction>
0085 #include <Libkleo/Formatting>
0086 #include <Libkleo/GnuPG>
0087 #include <Libkleo/KeyCache>
0088 #include <Libkleo/KeyListModel>
0089 #include <Libkleo/KeyListSortFilterProxyModel>
0090 #include <Libkleo/Stl_Util>
0091 #include <Libkleo/SystemInfo>
0092 
0093 #include <KSharedConfig>
0094 
0095 #ifdef Q_OS_UNIX
0096 #include <KWaylandExtras>
0097 #endif
0098 
0099 #include <chrono>
0100 #include <vector>
0101 using namespace std::chrono_literals;
0102 
0103 using namespace Kleo;
0104 using namespace Kleo::Commands;
0105 using namespace GpgME;
0106 
0107 static KGuiItem KStandardGuiItem_quit()
0108 {
0109     static const QString app = KAboutData::applicationData().displayName();
0110     KGuiItem item = KStandardGuiItem::quit();
0111     item.setText(xi18nc("@action:button", "&Quit <application>%1</application>", app));
0112     return item;
0113 }
0114 
0115 static KGuiItem KStandardGuiItem_close()
0116 {
0117     KGuiItem item = KStandardGuiItem::close();
0118     item.setText(i18nc("@action:button", "Only &Close Window"));
0119     return item;
0120 }
0121 
0122 static bool isQuitting = false;
0123 
0124 namespace
0125 {
0126 static const std::vector<QString> mainViewActionNames = {
0127     QStringLiteral("view_certificate_overview"),
0128     QStringLiteral("manage_smartcard"),
0129     QStringLiteral("pad_view"),
0130 };
0131 
0132 class CertificateView : public QWidget, public FocusFirstChild
0133 {
0134     Q_OBJECT
0135 public:
0136     CertificateView(QWidget *parent = nullptr)
0137         : QWidget{parent}
0138         , ui{this}
0139     {
0140     }
0141 
0142     SearchBar *searchBar() const
0143     {
0144         return ui.searchBar;
0145     }
0146 
0147     TabWidget *tabWidget() const
0148     {
0149         return ui.tabWidget;
0150     }
0151 
0152     void focusFirstChild(Qt::FocusReason reason) override
0153     {
0154         ui.searchBar->lineEdit()->setFocus(reason);
0155     }
0156 
0157 private:
0158     struct UI {
0159         TabWidget *tabWidget = nullptr;
0160         SearchBar *searchBar = nullptr;
0161         explicit UI(CertificateView *q)
0162         {
0163             auto vbox = new QVBoxLayout{q};
0164             vbox->setSpacing(0);
0165 
0166             searchBar = new SearchBar{q};
0167             vbox->addWidget(searchBar);
0168             tabWidget = new TabWidget{q};
0169             vbox->addWidget(tabWidget);
0170 
0171             tabWidget->connectSearchBar(searchBar);
0172         }
0173     } ui;
0174 };
0175 
0176 }
0177 
0178 class MainWindow::Private
0179 {
0180     friend class ::MainWindow;
0181     MainWindow *const q;
0182 
0183 public:
0184     explicit Private(MainWindow *qq);
0185     ~Private();
0186 
0187     template<typename T>
0188     void createAndStart()
0189     {
0190         (new T(this->currentView(), &this->controller))->start();
0191     }
0192     template<typename T>
0193     void createAndStart(QAbstractItemView *view)
0194     {
0195         (new T(view, &this->controller))->start();
0196     }
0197     template<typename T>
0198     void createAndStart(const QStringList &a)
0199     {
0200         (new T(a, this->currentView(), &this->controller))->start();
0201     }
0202     template<typename T>
0203     void createAndStart(const QStringList &a, QAbstractItemView *view)
0204     {
0205         (new T(a, view, &this->controller))->start();
0206     }
0207 
0208     void closeAndQuit()
0209     {
0210         const QString app = KAboutData::applicationData().displayName();
0211         const int rc = KMessageBox::questionTwoActionsCancel(q,
0212                                                              xi18n("<application>%1</application> may be used by other applications as a service.<nl/>"
0213                                                                    "You may instead want to close this window without exiting <application>%1</application>.",
0214                                                                    app),
0215                                                              i18nc("@title:window", "Really Quit?"),
0216                                                              KStandardGuiItem_close(),
0217                                                              KStandardGuiItem_quit(),
0218                                                              KStandardGuiItem::cancel(),
0219                                                              QLatin1StringView("really-quit-") + app.toLower());
0220         if (rc == KMessageBox::Cancel) {
0221             return;
0222         }
0223         isQuitting = true;
0224         if (!q->close()) {
0225             return;
0226         }
0227         // WARNING: 'this' might be deleted at this point!
0228         if (rc == KMessageBox::ButtonCode::SecondaryAction) {
0229             qApp->quit();
0230         }
0231     }
0232     void configureToolbars()
0233     {
0234         KEditToolBar dlg(q->factory());
0235         dlg.exec();
0236     }
0237     void editKeybindings()
0238     {
0239         KShortcutsDialog::showDialog(q->actionCollection(), KShortcutsEditor::LetterShortcutsAllowed, q);
0240         updateSearchBarClickMessage();
0241     }
0242 
0243     void updateSearchBarClickMessage()
0244     {
0245         const QString shortcutStr = focusToClickSearchAction->shortcut().toString();
0246         ui.searchTab->searchBar()->updateClickMessage(shortcutStr);
0247     }
0248 
0249     void updateStatusBar()
0250     {
0251         auto statusBar = std::make_unique<QStatusBar>();
0252         auto settings = KleopatraApplication::instance()->distributionSettings();
0253         bool showStatusbar = false;
0254         if (settings) {
0255             const QString statusline = settings->value(QStringLiteral("statusline"), {}).toString();
0256             if (!statusline.isEmpty()) {
0257                 auto customStatusLbl = new QLabel(statusline);
0258                 statusBar->insertWidget(0, customStatusLbl);
0259                 showStatusbar = true;
0260             }
0261         }
0262         if (DeVSCompliance::isActive()) {
0263             auto statusLbl = std::make_unique<QLabel>(DeVSCompliance::name());
0264             if (!SystemInfo::isHighContrastModeActive()) {
0265                 const auto color = KColorScheme(QPalette::Active, KColorScheme::View)
0266                                        .foreground(DeVSCompliance::isCompliant() ? KColorScheme::NormalText : KColorScheme::NegativeText)
0267                                        .color();
0268                 const auto background = KColorScheme(QPalette::Active, KColorScheme::View)
0269                                             .background(DeVSCompliance::isCompliant() ? KColorScheme::PositiveBackground : KColorScheme::NegativeBackground)
0270                                             .color();
0271                 statusLbl->setStyleSheet(QStringLiteral("QLabel { color: %1; background-color: %2; }").arg(color.name()).arg(background.name()));
0272             }
0273             statusBar->insertPermanentWidget(0, statusLbl.release());
0274             showStatusbar = true;
0275         }
0276 
0277         if (showStatusbar) {
0278             q->setStatusBar(statusBar.release()); // QMainWindow takes ownership
0279         } else {
0280             q->setStatusBar(nullptr);
0281         }
0282     }
0283 
0284     void selfTest()
0285     {
0286         createAndStart<SelfTestCommand>();
0287     }
0288 
0289     void configureGroups()
0290     {
0291         // open groups config dialog as independent top-level window
0292         KleopatraApplication::instance()->openOrRaiseGroupsConfigDialog(nullptr);
0293     }
0294 
0295     void showHandbook();
0296 
0297     void gnupgLogViewer()
0298     {
0299         // Warning: Don't assume that the program needs to be in PATH. On Windows, it will also be found next to the calling process.
0300         if (!QProcess::startDetached(QStringLiteral("kwatchgnupg"), QStringList()))
0301             KMessageBox::error(q,
0302                                i18n("Could not start the GnuPG Log Viewer (kwatchgnupg). "
0303                                     "Please check your installation."),
0304                                i18n("Error Starting KWatchGnuPG"));
0305     }
0306 
0307     void forceUpdateCheck()
0308     {
0309         UpdateNotification::forceUpdateCheck(q);
0310     }
0311 
0312     void slotConfigCommitted();
0313     void slotContextMenuRequested(QAbstractItemView *, const QPoint &p)
0314     {
0315         if (auto const menu = qobject_cast<QMenu *>(q->factory()->container(QStringLiteral("listview_popup"), q))) {
0316             menu->exec(p);
0317         } else {
0318             qCDebug(KLEOPATRA_LOG) << "no \"listview_popup\" <Menu> in kleopatra's ui.rc file";
0319         }
0320     }
0321 
0322     void slotFocusQuickSearch()
0323     {
0324         ui.searchTab->searchBar()->lineEdit()->setFocus();
0325     }
0326 
0327     void showView(const QString &actionName, QWidget *widget)
0328     {
0329         const auto coll = q->actionCollection();
0330         if (coll) {
0331             for (const QString &name : mainViewActionNames) {
0332                 if (auto action = coll->action(name)) {
0333                     action->setChecked(name == actionName);
0334                 }
0335             }
0336         }
0337         ui.stackWidget->setCurrentWidget(widget);
0338         if (auto ffci = dynamic_cast<Kleo::FocusFirstChild *>(widget)) {
0339             ffci->focusFirstChild(Qt::TabFocusReason);
0340         }
0341     }
0342 
0343     void showCertificateView()
0344     {
0345         if (KeyCache::instance()->keys().empty()) {
0346             showView(QStringLiteral("view_certificate_overview"), ui.welcomeWidget);
0347         } else {
0348             showView(QStringLiteral("view_certificate_overview"), ui.searchTab);
0349         }
0350     }
0351 
0352     void showSmartcardView()
0353     {
0354         showView(QStringLiteral("manage_smartcard"), ui.scWidget);
0355     }
0356 
0357     void showPadView()
0358     {
0359         if (!ui.padWidget) {
0360             ui.padWidget = new PadWidget;
0361             ui.stackWidget->addWidget(ui.padWidget);
0362         }
0363         showView(QStringLiteral("pad_view"), ui.padWidget);
0364         ui.stackWidget->resize(ui.padWidget->sizeHint());
0365     }
0366 
0367     void restartDaemons()
0368     {
0369         Kleo::killDaemons();
0370     }
0371 
0372 private:
0373     void setupActions();
0374 
0375     QAbstractItemView *currentView() const
0376     {
0377         return ui.searchTab->tabWidget()->currentView();
0378     }
0379 
0380     void keyListingDone()
0381     {
0382         const auto curWidget = ui.stackWidget->currentWidget();
0383         if (curWidget == ui.scWidget || curWidget == ui.padWidget) {
0384             return;
0385         }
0386         showCertificateView();
0387     }
0388 
0389 private:
0390     Kleo::KeyListController controller;
0391     bool firstShow : 1;
0392     struct UI {
0393         CertificateView *searchTab = nullptr;
0394         PadWidget *padWidget = nullptr;
0395         SmartCardWidget *scWidget = nullptr;
0396         WelcomeWidget *welcomeWidget = nullptr;
0397         QStackedWidget *stackWidget = nullptr;
0398         explicit UI(MainWindow *q);
0399     } ui;
0400     QAction *focusToClickSearchAction = nullptr;
0401     ClipboardMenu *clipboadMenu = nullptr;
0402 };
0403 
0404 MainWindow::Private::UI::UI(MainWindow *q)
0405     : padWidget(nullptr)
0406 {
0407     auto mainWidget = new QWidget{q};
0408     auto mainLayout = new QVBoxLayout(mainWidget);
0409     mainLayout->setContentsMargins({});
0410     stackWidget = new QStackedWidget{q};
0411 
0412     searchTab = new CertificateView{q};
0413     stackWidget->addWidget(searchTab);
0414 
0415     new KeyCacheOverlay(mainWidget, q);
0416 
0417     scWidget = new SmartCardWidget{q};
0418     stackWidget->addWidget(scWidget);
0419 
0420     welcomeWidget = new WelcomeWidget{q};
0421     stackWidget->addWidget(welcomeWidget);
0422 
0423     mainLayout->addWidget(stackWidget);
0424 
0425     q->setCentralWidget(mainWidget);
0426 }
0427 
0428 MainWindow::Private::Private(MainWindow *qq)
0429     : q(qq)
0430     , controller(q)
0431     , firstShow(true)
0432     , ui(q)
0433 {
0434     KDAB_SET_OBJECT_NAME(controller);
0435 
0436     AbstractKeyListModel *flatModel = AbstractKeyListModel::createFlatKeyListModel(q);
0437     AbstractKeyListModel *hierarchicalModel = AbstractKeyListModel::createHierarchicalKeyListModel(q);
0438 
0439     KDAB_SET_OBJECT_NAME(flatModel);
0440     KDAB_SET_OBJECT_NAME(hierarchicalModel);
0441 
0442 #if GPGME_VERSION_NUMBER >= 0x011800 // 1.24.0
0443     auto keyExportDragHandler = std::make_shared<KeyExportDragHandler>();
0444     flatModel->setDragHandler(keyExportDragHandler);
0445     hierarchicalModel->setDragHandler(keyExportDragHandler);
0446 #endif
0447 
0448     controller.setFlatModel(flatModel);
0449     controller.setHierarchicalModel(hierarchicalModel);
0450     controller.setTabWidget(ui.searchTab->tabWidget());
0451 
0452     ui.searchTab->tabWidget()->setFlatModel(flatModel);
0453     ui.searchTab->tabWidget()->setHierarchicalModel(hierarchicalModel);
0454 
0455 #ifdef Q_OS_UNIX
0456     connect(KWaylandExtras::self(), &KWaylandExtras::windowExported, q, [this](const auto &window, const auto &token) {
0457         if (window == q->windowHandle()) {
0458             qputenv("PINENTRY_GEOM_HINT", QUrl::toPercentEncoding(token));
0459         }
0460     });
0461     q->exportWindow();
0462 #endif
0463 
0464     setupActions();
0465 
0466     ui.stackWidget->setCurrentWidget(ui.searchTab);
0467     if (auto action = q->actionCollection()->action(QStringLiteral("view_certificate_overview"))) {
0468         action->setChecked(true);
0469     }
0470 
0471     connect(&controller, SIGNAL(contextMenuRequested(QAbstractItemView *, QPoint)), q, SLOT(slotContextMenuRequested(QAbstractItemView *, QPoint)));
0472     connect(KeyCache::instance().get(), &KeyCache::keyListingDone, q, [this]() {
0473         keyListingDone();
0474     });
0475 
0476     q->createGUI(QStringLiteral("kleopatra.rc"));
0477 
0478     // make toolbar buttons accessible by keyboard
0479     auto toolbar = q->findChild<KToolBar *>();
0480     if (toolbar) {
0481         auto toolbarButtons = toolbar->findChildren<QToolButton *>();
0482         for (auto b : toolbarButtons) {
0483             b->setFocusPolicy(Qt::TabFocus);
0484         }
0485         // move toolbar and its child widgets before the central widget in the tab order;
0486         // this is necessary to make Shift+Tab work as expected
0487         forceSetTabOrder(q, toolbar);
0488         auto toolbarChildren = toolbar->findChildren<QWidget *>();
0489         std::for_each(std::rbegin(toolbarChildren), std::rend(toolbarChildren), [toolbar](auto w) {
0490             forceSetTabOrder(toolbar, w);
0491         });
0492     }
0493 
0494     if (auto action = q->actionCollection()->action(QStringLiteral("help_whats_this"))) {
0495         delete action;
0496     }
0497 
0498     q->setAcceptDrops(true);
0499 
0500     // set default window size
0501     q->resize(QSize(1024, 500));
0502     q->setAutoSaveSettings();
0503 
0504     updateSearchBarClickMessage();
0505     updateStatusBar();
0506 
0507     if (KeyCache::instance()->initialized()) {
0508         keyListingDone();
0509     }
0510 
0511     // delay setting the models to use the key cache so that the UI (including
0512     // the "Loading certificate cache..." overlay) is shown before the
0513     // blocking key cache initialization happens
0514     QMetaObject::invokeMethod(
0515         q,
0516         [flatModel, hierarchicalModel]() {
0517             flatModel->useKeyCache(true, KeyList::AllKeys);
0518             hierarchicalModel->useKeyCache(true, KeyList::AllKeys);
0519         },
0520         Qt::QueuedConnection);
0521 }
0522 
0523 MainWindow::Private::~Private()
0524 {
0525 }
0526 
0527 MainWindow::MainWindow(QWidget *parent, Qt::WindowFlags flags)
0528     : KXmlGuiWindow(parent, flags)
0529     , d(new Private(this))
0530 {
0531 }
0532 
0533 MainWindow::~MainWindow()
0534 {
0535 }
0536 
0537 void MainWindow::Private::setupActions()
0538 {
0539     KActionCollection *const coll = q->actionCollection();
0540 
0541     const std::vector<action_data> action_data = {
0542     // see keylistcontroller.cpp for more actions
0543     // Tools menu
0544 #ifndef Q_OS_WIN
0545         {
0546             "tools_start_kwatchgnupg",
0547             i18n("GnuPG Log Viewer"),
0548             QString(),
0549             "kwatchgnupg",
0550             q,
0551             [this](bool) {
0552                 gnupgLogViewer();
0553             },
0554             QString(),
0555         },
0556 #endif
0557         {
0558             "tools_restart_backend",
0559             i18nc("@action:inmenu", "Restart Background Processes"),
0560             i18nc("@info:tooltip", "Restart the background processes, e.g. after making changes to the configuration."),
0561             "view-refresh",
0562             q,
0563             [this](bool) {
0564                 restartDaemons();
0565             },
0566             {},
0567         },
0568     // Help menu
0569 #ifdef Q_OS_WIN
0570         {
0571             "help_check_updates",
0572             i18n("Check for updates"),
0573             QString(),
0574             "gpg4win-compact",
0575             q,
0576             [this](bool) {
0577                 forceUpdateCheck();
0578             },
0579             QString(),
0580         },
0581 #endif
0582         // View menu
0583         {
0584             "view_certificate_overview",
0585             i18nc("@action show certificate overview", "Certificates"),
0586             i18n("Show certificate overview"),
0587             "view-certificate",
0588             q,
0589             [this](bool) {
0590                 showCertificateView();
0591             },
0592             QString(),
0593         },
0594         {
0595             "pad_view",
0596             i18nc("@action show input / output area for encrypting/signing resp. decrypting/verifying text", "Notepad"),
0597             i18n("Show pad for encrypting/decrypting and signing/verifying text"),
0598             "note",
0599             q,
0600             [this](bool) {
0601                 showPadView();
0602             },
0603             QString(),
0604         },
0605         {
0606             "manage_smartcard",
0607             i18nc("@action show smartcard management view", "Smartcards"),
0608             i18n("Show smartcard management"),
0609             "auth-sim-locked",
0610             q,
0611             [this](bool) {
0612                 showSmartcardView();
0613             },
0614             QString(),
0615         },
0616         // Settings menu
0617         {
0618             "settings_self_test",
0619             i18n("Perform Self-Test"),
0620             QString(),
0621             nullptr,
0622             q,
0623             [this](bool) {
0624                 selfTest();
0625             },
0626             QString(),
0627         },
0628         {
0629             "configure_groups",
0630             i18n("Configure Groups..."),
0631             QString(),
0632             "group",
0633             q,
0634             [this](bool) {
0635                 configureGroups();
0636             },
0637             QString(),
0638         }};
0639 
0640     make_actions_from_data(action_data, coll);
0641 
0642     if (!Settings().groupsEnabled()) {
0643         if (auto action = coll->action(QStringLiteral("configure_groups"))) {
0644             delete action;
0645         }
0646     }
0647 
0648     for (const QString &name : mainViewActionNames) {
0649         if (auto action = coll->action(name)) {
0650             action->setCheckable(true);
0651         }
0652     }
0653 
0654     KStandardAction::close(q, SLOT(close()), coll);
0655     KStandardAction::quit(q, SLOT(closeAndQuit()), coll);
0656     KStandardAction::configureToolbars(q, SLOT(configureToolbars()), coll);
0657     KStandardAction::keyBindings(q, SLOT(editKeybindings()), coll);
0658     KStandardAction::preferences(qApp, SLOT(openOrRaiseConfigDialog()), coll);
0659 
0660     focusToClickSearchAction = new QAction(i18n("Set Focus to Quick Search"), q);
0661     coll->addAction(QStringLiteral("focus_to_quickseach"), focusToClickSearchAction);
0662     coll->setDefaultShortcut(focusToClickSearchAction, QKeySequence(Qt::ALT | Qt::Key_Q));
0663     connect(focusToClickSearchAction, SIGNAL(triggered(bool)), q, SLOT(slotFocusQuickSearch()));
0664     clipboadMenu = new ClipboardMenu(q);
0665     clipboadMenu->setMainWindow(q);
0666     clipboadMenu->clipboardMenu()->setIcon(QIcon::fromTheme(QStringLiteral("edit-paste")));
0667     clipboadMenu->clipboardMenu()->setPopupMode(QToolButton::InstantPopup);
0668     coll->addAction(QStringLiteral("clipboard_menu"), clipboadMenu->clipboardMenu());
0669 
0670     /* Add additional help actions for documentation */
0671     const auto compendium = new DocAction(QIcon::fromTheme(QStringLiteral("gpg4win-compact")),
0672                                           i18n("Gpg4win Compendium"),
0673                                           i18nc("The Gpg4win compendium is only available"
0674                                                 "at this point (24.7.2017) in german and english."
0675                                                 "Please check with Gpg4win before translating this filename.",
0676                                                 "gpg4win-compendium-en.pdf"),
0677                                           QStringLiteral("../share/gpg4win"),
0678                                           coll);
0679     coll->addAction(QStringLiteral("help_doc_compendium"), compendium);
0680 
0681     /* Documentation centered around the german approved VS-NfD mode for official
0682      * RESTRICTED communication. This is only available in some distributions with
0683      * the focus on official communications. */
0684     const auto quickguide =
0685         new DocAction(QIcon::fromTheme(QStringLiteral("help-contextual")),
0686                       i18n("&Quickguide"),
0687                       i18nc("Only available in German and English. Leave to English for other languages.", "encrypt_and_sign_gnupgvsd_en.pdf"),
0688                       QStringLiteral("../share/doc/gnupg-vsd"),
0689                       coll);
0690     coll->addAction(QStringLiteral("help_doc_quickguide"), quickguide);
0691 
0692     const auto symguide =
0693         new DocAction(QIcon::fromTheme(QStringLiteral("help-contextual")),
0694                       i18n("&Password-based encryption"),
0695                       i18nc("Only available in German and English. Leave to English for other languages.", "symmetric_encryption_gnupgvsd_en.pdf"),
0696                       QStringLiteral("../share/doc/gnupg-vsd"),
0697                       coll);
0698     coll->addAction(QStringLiteral("help_doc_symenc"), symguide);
0699 
0700     const auto groups = new DocAction(QIcon::fromTheme(QStringLiteral("help-contextual")),
0701                                       i18n("&Group configuration"),
0702                                       i18nc("Only available in German and English. Leave to English for other languages.", "groupfeature_gnupgvsd_en.pdf"),
0703                                       QStringLiteral("../share/doc/gnupg-vsd"),
0704                                       coll);
0705     coll->addAction(QStringLiteral("help_doc_groups"), groups);
0706 
0707 #ifdef Q_OS_WIN
0708     const auto gpgol =
0709         new DocAction(QIcon::fromTheme(QStringLiteral("help-contextual")),
0710                       i18n("&Mail encryption in Outlook"),
0711                       i18nc("Only available in German and English. Leave to English for other languages. Only shown on Windows.", "gpgol_outlook_addin_en.pdf"),
0712                       QStringLiteral("../share/doc/gnupg-vsd"),
0713                       coll);
0714     coll->addAction(QStringLiteral("help_doc_gpgol"), gpgol);
0715 #endif
0716 
0717     /* The submenu with advanced topics */
0718     const auto certmngmnt =
0719         new DocAction(QIcon::fromTheme(QStringLiteral("help-contextual")),
0720                       i18n("&Certification Management"),
0721                       i18nc("Only available in German and English. Leave to English for other languages.", "certification_management_gnupgvsd_en.pdf"),
0722                       QStringLiteral("../share/doc/gnupg-vsd"),
0723                       coll);
0724     coll->addAction(QStringLiteral("help_doc_cert_management"), certmngmnt);
0725 
0726     const auto smartcard =
0727         new DocAction(QIcon::fromTheme(QStringLiteral("help-contextual")),
0728                       i18n("&Smartcard setup"),
0729                       i18nc("Only available in German and English. Leave to English for other languages.", "smartcard_setup_gnupgvsd_en.pdf"),
0730                       QStringLiteral("../share/doc/gnupg-vsd"),
0731                       coll);
0732     coll->addAction(QStringLiteral("help_doc_smartcard"), smartcard);
0733 
0734     const auto man_gnupg = new DocAction(QIcon::fromTheme(QStringLiteral("help-contextual")),
0735                                          i18n("GnuPG Command&line"),
0736                                          QStringLiteral("gnupg_manual_en.pdf"),
0737                                          QStringLiteral("../share/doc/gnupg-vsd"),
0738                                          coll);
0739     coll->addAction(QStringLiteral("help_doc_gnupg"), man_gnupg);
0740 
0741     /* The secops */
0742     const auto vsa10573 =
0743         new DocAction(QIcon::fromTheme(QStringLiteral("dvipdf")),
0744                       i18n("SecOps VSA-10573"),
0745                       i18nc("Only available in German and English. Leave to English for other languages.", "BSI-VSA-10573-ENG_secops-20220207.pdf"),
0746                       QStringLiteral("../share/doc/gnupg-vsd"),
0747                       coll);
0748     coll->addAction(QStringLiteral("help_doc_vsa10573"), vsa10573);
0749 
0750     const auto vsa10584 =
0751         new DocAction(QIcon::fromTheme(QStringLiteral("dvipdf")),
0752                       i18n("SecOps VSA-10584"),
0753                       i18nc("Only available in German and English. Leave to English for other languages.", "BSI-VSA-10584-ENG_secops-20220207.pdf"),
0754                       QStringLiteral("../share/doc/gnupg-vsd"),
0755                       coll);
0756     coll->addAction(QStringLiteral("help_doc_vsa10584"), vsa10584);
0757 
0758     q->setStandardToolBarMenuEnabled(true);
0759 
0760     controller.createActions(coll);
0761 
0762     ui.searchTab->tabWidget()->createActions(coll);
0763 }
0764 
0765 void MainWindow::Private::slotConfigCommitted()
0766 {
0767     controller.updateConfig();
0768     updateStatusBar();
0769 }
0770 
0771 void MainWindow::closeEvent(QCloseEvent *e)
0772 {
0773     // KMainWindow::closeEvent() insists on quitting the application,
0774     // so do not let it touch the event...
0775     qCDebug(KLEOPATRA_LOG);
0776     if (d->controller.hasRunningCommands()) {
0777         if (d->controller.shutdownWarningRequired()) {
0778             const int ret = KMessageBox::warningContinueCancel(this,
0779                                                                i18n("There are still some background operations ongoing. "
0780                                                                     "These will be terminated when closing the window. "
0781                                                                     "Proceed?"),
0782                                                                i18n("Ongoing Background Tasks"));
0783             if (ret != KMessageBox::Continue) {
0784                 e->ignore();
0785                 return;
0786             }
0787         }
0788         d->controller.cancelCommands();
0789         if (d->controller.hasRunningCommands()) {
0790             // wait for them to be finished:
0791             setEnabled(false);
0792             QEventLoop ev;
0793             QTimer::singleShot(100ms, &ev, &QEventLoop::quit);
0794             connect(&d->controller, &KeyListController::commandsExecuting, &ev, &QEventLoop::quit);
0795             ev.exec();
0796             if (d->controller.hasRunningCommands())
0797                 qCWarning(KLEOPATRA_LOG) << "controller still has commands running, this may crash now...";
0798             setEnabled(true);
0799         }
0800     }
0801     unexportWindow();
0802     if (isQuitting || qApp->isSavingSession()) {
0803         d->ui.searchTab->tabWidget()->saveViews(KSharedConfig::openConfig().data());
0804         KConfigGroup grp(KConfigGroup(KSharedConfig::openConfig(), autoSaveGroup()));
0805         saveMainWindowSettings(grp);
0806         e->accept();
0807     } else {
0808         e->ignore();
0809         hide();
0810     }
0811 }
0812 
0813 void MainWindow::showEvent(QShowEvent *e)
0814 {
0815     KXmlGuiWindow::showEvent(e);
0816     if (d->firstShow) {
0817         d->ui.searchTab->tabWidget()->loadViews(KSharedConfig::openConfig().data());
0818         d->firstShow = false;
0819     }
0820 
0821     if (!savedGeometry.isEmpty()) {
0822         restoreGeometry(savedGeometry);
0823     }
0824 }
0825 
0826 void MainWindow::hideEvent(QHideEvent *e)
0827 {
0828     savedGeometry = saveGeometry();
0829     KXmlGuiWindow::hideEvent(e);
0830 }
0831 
0832 void MainWindow::importCertificatesFromFile(const QStringList &files)
0833 {
0834     if (!files.empty()) {
0835         d->createAndStart<ImportCertificateFromFileCommand>(files);
0836     }
0837 }
0838 
0839 static QStringList extract_local_files(const QMimeData *data)
0840 {
0841     const QList<QUrl> urls = data->urls();
0842     // begin workaround KDE/Qt misinterpretation of text/uri-list
0843     QList<QUrl>::const_iterator end = urls.end();
0844     if (urls.size() > 1 && !urls.back().isValid()) {
0845         --end;
0846     }
0847     // end workaround
0848     QStringList result;
0849     std::transform(urls.begin(), end, std::back_inserter(result), std::mem_fn(&QUrl::toLocalFile));
0850     result.erase(std::remove_if(result.begin(), result.end(), std::mem_fn(&QString::isEmpty)), result.end());
0851     return result;
0852 }
0853 
0854 static bool can_decode_local_files(const QMimeData *data)
0855 {
0856     if (!data) {
0857         return false;
0858     }
0859     return !extract_local_files(data).empty();
0860 }
0861 
0862 void MainWindow::dragEnterEvent(QDragEnterEvent *e)
0863 {
0864     qCDebug(KLEOPATRA_LOG);
0865 
0866     if (can_decode_local_files(e->mimeData())) {
0867         e->acceptProposedAction();
0868     }
0869 }
0870 
0871 void MainWindow::dropEvent(QDropEvent *e)
0872 {
0873     qCDebug(KLEOPATRA_LOG);
0874 
0875     if (!can_decode_local_files(e->mimeData())) {
0876         return;
0877     }
0878 
0879     e->setDropAction(Qt::CopyAction);
0880 
0881     const QStringList files = extract_local_files(e->mimeData());
0882 
0883     KleopatraApplication::instance()->handleFiles(files);
0884 
0885     e->accept();
0886 }
0887 
0888 void MainWindow::readProperties(const KConfigGroup &cg)
0889 {
0890     qCDebug(KLEOPATRA_LOG);
0891     KXmlGuiWindow::readProperties(cg);
0892     setHidden(cg.readEntry("hidden", false));
0893 }
0894 
0895 void MainWindow::saveProperties(KConfigGroup &cg)
0896 {
0897     qCDebug(KLEOPATRA_LOG);
0898     KXmlGuiWindow::saveProperties(cg);
0899     cg.writeEntry("hidden", isHidden());
0900 }
0901 
0902 void MainWindow::exportWindow()
0903 {
0904 #ifdef Q_OS_UNIX
0905     (void)winId(); // Ensures that windowHandle() returns the window
0906     KWaylandExtras::self()->exportWindow(windowHandle());
0907 #endif
0908 }
0909 
0910 void MainWindow::unexportWindow()
0911 {
0912 #ifdef Q_OS_UNIX
0913     KWaylandExtras::self()->unexportWindow(windowHandle());
0914 #endif
0915 }
0916 
0917 #include "mainwindow.moc"
0918 #include "moc_mainwindow.cpp"