diff --git a/src/commands/selftestcommand.cpp b/src/commands/selftestcommand.cpp index 9c52570bc..e0d6a0a58 100644 --- a/src/commands/selftestcommand.cpp +++ b/src/commands/selftestcommand.cpp @@ -1,269 +1,268 @@ /* -*- mode: c++; c-basic-offset:4 -*- commands/selftestcommand.cpp This file is part of Kleopatra, the KDE keymanager SPDX-FileCopyrightText: 2008 Klarälvdalens Datakonsult AB SPDX-License-Identifier: GPL-2.0-or-later */ #include #include "selftestcommand.h" #include "command_p.h" #include #include "kleopatra_debug.h" #ifdef Q_OS_WIN # include #endif #include #include #ifdef HAVE_KLEOPATRACLIENT_LIBRARY # include #endif #include #include #include #include #include #include #include #include #include using namespace Kleo; using namespace Kleo::Commands; using namespace Kleo::Dialogs; static const char *const components[] = { nullptr, // gpgconf "gpg", "gpg-agent", "scdaemon", "gpgsm", "dirmngr", }; static const unsigned int numComponents = sizeof components / sizeof * components; class SelfTestCommand::Private : Command::Private { friend class ::Kleo::Commands::SelfTestCommand; SelfTestCommand *q_func() const { return static_cast(q); } public: explicit Private(SelfTestCommand *qq, KeyListController *c); ~Private() override; private: void init(); void ensureDialogCreated() { if (dialog) { return; } dialog = new SelfTestDialog; applyWindowID(dialog); dialog->setAttribute(Qt::WA_DeleteOnClose); connect(dialog, &SelfTestDialog::updateRequested, q_func(), [this]() { slotUpdateRequested(); }); connect(dialog, &QDialog::accepted, q_func(), [this]() { slotDialogAccepted(); }); connect(dialog, &QDialog::rejected, q_func(), [this]() { slotDialogRejected(); }); dialog->setRunAtStartUp(runAtStartUp()); dialog->setAutomaticMode(automatic); } void ensureDialogShown() { ensureDialogCreated(); if (dialog->isVisible()) { dialog->raise(); } else { dialog->show(); } } bool runAtStartUp() const { const KConfigGroup config(KSharedConfig::openConfig(), "Self-Test"); return config.readEntry("run-at-startup", true); } void setRunAtStartUp(bool on) { KConfigGroup config(KSharedConfig::openConfig(), "Self-Test"); config.writeEntry("run-at-startup", on); } void runTests() { std::vector< std::shared_ptr > tests; #if defined(Q_OS_WIN) qCDebug(KLEOPATRA_LOG) << "Checking Windows Registry..."; tests.push_back(makeGpgProgramRegistryCheckSelfTest()); #if defined(HAVE_KLEOPATRACLIENT_LIBRARY) qCDebug(KLEOPATRA_LOG) << "Checking Ui Server connectivity..."; tests.push_back(makeUiServerConnectivitySelfTest()); #endif #endif qCDebug(KLEOPATRA_LOG) << "Checking gpg installation..."; tests.push_back(makeGpgEngineCheckSelfTest()); qCDebug(KLEOPATRA_LOG) << "Checking gpgsm installation..."; tests.push_back(makeGpgSmEngineCheckSelfTest()); qCDebug(KLEOPATRA_LOG) << "Checking gpgconf installation..."; tests.push_back(makeGpgConfEngineCheckSelfTest()); for (unsigned int i = 0; i < numComponents; ++i) { qCDebug(KLEOPATRA_LOG) << "Checking configuration of:" << components[i]; tests.push_back(makeGpgConfCheckConfigurationSelfTest(components[i])); } #ifndef Q_OS_WIN tests.push_back(makeGpgAgentConnectivitySelfTest()); #endif tests.push_back(makeDeVSComplianceCheckSelfTest()); tests.push_back(makeLibKleopatraRcSelfTest()); if (!dialog && std::none_of(tests.cbegin(), tests.cend(), [](const std::shared_ptr &test) { return test->failed(); })) { finished(); return; } ensureDialogCreated(); - dialog->clear(); - dialog->addSelfTests(tests); + dialog->setTests(tests); ensureDialogShown(); } private: void slotDialogAccepted() { setRunAtStartUp(dialog->runAtStartUp()); finished(); } void slotDialogRejected() { if (automatic) { canceled = true; Command::Private::canceled(); } else { slotDialogAccepted(); } } void slotUpdateRequested() { const auto conf = QGpgME::cryptoConfig(); if (conf) { conf->clear(); } runTests(); } private: QPointer dialog; bool canceled; bool automatic; }; SelfTestCommand::Private *SelfTestCommand::d_func() { return static_cast(d.get()); } const SelfTestCommand::Private *SelfTestCommand::d_func() const { return static_cast(d.get()); } #define d d_func() #define q q_func() SelfTestCommand::Private::Private(SelfTestCommand *qq, KeyListController *c) : Command::Private(qq, c), dialog(), canceled(false), automatic(false) { } SelfTestCommand::Private::~Private() { } SelfTestCommand::SelfTestCommand(KeyListController *c) : Command(new Private(this, c)) { d->init(); } SelfTestCommand::SelfTestCommand(QAbstractItemView *v, KeyListController *c) : Command(v, new Private(this, c)) { d->init(); } void SelfTestCommand::Private::init() { } SelfTestCommand::~SelfTestCommand() {} void SelfTestCommand::setAutomaticMode(bool on) { d->automatic = on; if (d->dialog) { d->dialog->setAutomaticMode(on); } } bool SelfTestCommand::isCanceled() const { return d->canceled; } void SelfTestCommand::doStart() { if (d->automatic) { if (!d->runAtStartUp()) { d->finished(); return; } } else { d->ensureDialogCreated(); } d->runTests(); } void SelfTestCommand::doCancel() { d->canceled = true; if (d->dialog) { d->dialog->close(); } d->dialog = nullptr; } #undef d #undef q #include "moc_selftestcommand.cpp" diff --git a/src/dialogs/selftestdialog.cpp b/src/dialogs/selftestdialog.cpp index 947ad6816..52073a22c 100644 --- a/src/dialogs/selftestdialog.cpp +++ b/src/dialogs/selftestdialog.cpp @@ -1,491 +1,481 @@ /* -*- mode: c++; c-basic-offset:4 -*- dialogs/selftestdialog.cpp This file is part of Kleopatra, the KDE keymanager SPDX-FileCopyrightText: 2008 Klarälvdalens Datakonsult AB SPDX-License-Identifier: GPL-2.0-or-later */ #include #include "selftestdialog.h" #include #include #include #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; namespace { class Model : public QAbstractTableModel { Q_OBJECT public: explicit Model(QObject *parent = nullptr) : QAbstractTableModel(parent), m_tests() { } enum Column { TestName, TestResult, NumColumns }; const std::shared_ptr &fromModelIndex(const QModelIndex &idx) const { const unsigned int row = idx.row(); if (row < m_tests.size()) { return m_tests[row]; } static const std::shared_ptr null; return null; } int rowCount(const QModelIndex &idx) const override { return idx.isValid() ? 0 : m_tests.size(); } int columnCount(const QModelIndex &) const override { return NumColumns; } QVariant data(const QModelIndex &idx, int role) const override { const unsigned int row = idx.row(); if (idx.isValid() && row < m_tests.size()) switch (role) { case Qt::DisplayRole: case Qt::ToolTipRole: switch (idx.column()) { case TestName: return m_tests[row]->name(); case TestResult: return m_tests[row]->skipped() ? i18n("Skipped") : m_tests[row]->passed() ? i18n("Passed") : /* else */ m_tests[row]->shortError(); } break; case Qt::BackgroundRole: if (!SystemInfo::isHighContrastModeActive()) { KColorScheme scheme(qApp->palette().currentColorGroup()); return (m_tests[row]->skipped() ? scheme.background(KColorScheme::NeutralBackground) : m_tests[row]->passed() ? scheme.background(KColorScheme::PositiveBackground) : scheme.background(KColorScheme::NegativeBackground)).color(); } } return QVariant(); } QVariant headerData(int section, Qt::Orientation o, int role) const override { if (o == Qt::Horizontal && section >= 0 && section < NumColumns && role == Qt::DisplayRole) switch (section) { case TestName: return i18n("Test Name"); case TestResult: return i18n("Result"); } return QVariant(); } void clear() { if (m_tests.empty()) { return; } beginRemoveRows(QModelIndex(), 0, m_tests.size() - 1); m_tests.clear(); endRemoveRows(); } void append(const std::vector> &tests) { if (tests.empty()) { return; } beginInsertRows(QModelIndex(), m_tests.size(), m_tests.size() + tests.size()); m_tests.insert(m_tests.end(), tests.begin(), tests.end()); endInsertRows(); } void reloadData() { if (!m_tests.empty()) { Q_EMIT dataChanged(index(0, 0), index(m_tests.size() - 1, NumColumns - 1)); } } const std::shared_ptr &at(unsigned int idx) const { return m_tests.at(idx); } private: std::vector> m_tests; }; class Proxy : public QSortFilterProxyModel { Q_OBJECT public: explicit Proxy(QObject *parent = nullptr) : QSortFilterProxyModel(parent), m_showAll(true) { setDynamicSortFilter(true); } bool showAll() const { return m_showAll; } Q_SIGNALS: void showAllChanged(bool); public Q_SLOTS: void setShowAll(bool on) { if (on == m_showAll) { return; } m_showAll = on; invalidateFilter(); Q_EMIT showAllChanged(on); } private: bool filterAcceptsRow(int src_row, const QModelIndex &src_parent) const override { if (m_showAll) { return true; } if (const Model *const model = qobject_cast(sourceModel())) { if (!src_parent.isValid() && src_row >= 0 && src_row < model->rowCount(src_parent)) { if (const std::shared_ptr &t = model->at(src_row)) { return !t->passed(); } else { qCWarning(KLEOPATRA_LOG) << "NULL test??"; } } else { if (src_parent.isValid()) { qCWarning(KLEOPATRA_LOG) << "view asks for subitems!"; } else { qCWarning(KLEOPATRA_LOG) << "index " << src_row << " is out of range [" << 0 << "," << model->rowCount(src_parent) << "]"; } } } else { qCWarning(KLEOPATRA_LOG) << "expected a ::Model, got "; if (!sourceModel()) { qCWarning(KLEOPATRA_LOG) << "a null pointer"; } else { qCWarning(KLEOPATRA_LOG) << sourceModel()->metaObject()->className(); } } return false; } private: bool m_showAll; }; } class SelfTestDialog::Private { friend class ::Kleo::Dialogs::SelfTestDialog; SelfTestDialog *const q; public: explicit Private(SelfTestDialog *qq) : q(qq), model(q), proxy(q), ui(q) { proxy.setSourceModel(&model); ui.resultsTV->setModel(&proxy); ui.detailsGB->hide(); ui.proposedCorrectiveActionGB->hide(); connect(ui.buttonBox, &QDialogButtonBox::accepted, q, &QDialog::accept); connect(ui.buttonBox, &QDialogButtonBox::rejected, q, &QDialog::reject); connect(ui.doItPB, &QAbstractButton::clicked, q, [this]() { slotDoItClicked(); }); connect(ui.rerunPB, &QAbstractButton::clicked, q, &SelfTestDialog::updateRequested); connect(ui.resultsTV->selectionModel(), &QItemSelectionModel::selectionChanged, q, [this]() { slotSelectionChanged(); }); connect(ui.showAllCB, &QAbstractButton::toggled, &proxy, &Proxy::setShowAll); proxy.setShowAll(ui.showAllCB->isChecked()); ui.resultsTV->setFocus(); } private: void slotSelectionChanged() { const int row = selectedRowIndex(); if (row < 0) { ui.detailsLB->setText(i18n("(select test first)")); ui.detailsGB->hide(); ui.proposedCorrectiveActionGB->hide(); } else { const std::shared_ptr &t = model.at(row); ui.detailsLB->setText(t->longError()); ui.detailsGB->setVisible(!t->passed()); const QString action = t->proposedFix(); ui.proposedCorrectiveActionGB->setVisible(!t->passed() && !action.isEmpty()); ui.proposedCorrectiveActionLB->setText(action); ui.doItPB->setVisible(!t->passed() && t->canFixAutomatically()); } } void slotDoItClicked() { if (const std::shared_ptr st = model.fromModelIndex(selectedRow())) if (st->fix()) { model.reloadData(); } } private: void updateColumnSizes() { ui.resultsTV->header()->resizeSections(QHeaderView::ResizeToContents); } private: QModelIndex selectedRow() const { const QItemSelectionModel *const ism = ui.resultsTV->selectionModel(); if (!ism) { return QModelIndex(); } const QModelIndexList mil = ism->selectedRows(); return mil.empty() ? QModelIndex() : proxy.mapToSource(mil.front()); } int selectedRowIndex() const { return selectedRow().row(); } private: Model model; Proxy proxy; struct UI { NavigatableTreeView *resultsTV = nullptr; QCheckBox *showAllCB = nullptr; QGroupBox *detailsGB = nullptr; QLabel *detailsLB = nullptr; QGroupBox *proposedCorrectiveActionGB = nullptr; QLabel *proposedCorrectiveActionLB = nullptr; QPushButton *doItPB = nullptr; QCheckBox *runAtStartUpCB; QDialogButtonBox *buttonBox; QPushButton *rerunPB = nullptr; LabelHelper labelHelper; explicit UI(SelfTestDialog *qq) { auto mainLayout = new QVBoxLayout{qq}; { auto label = new QLabel{xi18n( "These are the results of the Kleopatra self-test suite. Click on a test for details." "Note that all but the first failure might be due to prior tests failing."), qq}; label->setWordWrap(true); labelHelper.addLabel(label); mainLayout->addWidget(label); } auto splitter = new QSplitter{qq}; splitter->setOrientation(Qt::Vertical); { auto widget = new QWidget{qq}; auto vbox = new QVBoxLayout{widget}; vbox->setContentsMargins(0, 0, 0, 0); resultsTV = new NavigatableTreeView{qq}; QSizePolicy sizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding); sizePolicy.setHorizontalStretch(0); sizePolicy.setVerticalStretch(1); sizePolicy.setHeightForWidth(resultsTV->sizePolicy().hasHeightForWidth()); resultsTV->setSizePolicy(sizePolicy); resultsTV->setMinimumHeight(100); resultsTV->setRootIsDecorated(false); resultsTV->setAllColumnsShowFocus(true); vbox->addWidget(resultsTV); splitter->addWidget(widget); } { detailsGB = new QGroupBox{i18nc("@title:group", "Details"), qq}; auto groupBoxLayout = new QVBoxLayout{detailsGB}; auto scrollArea = new Kleo::ScrollArea{qq}; scrollArea->setFocusPolicy(Qt::NoFocus); scrollArea->setMinimumHeight(100); scrollArea->setFrameShape(QFrame::NoFrame); scrollArea->setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff); auto scrollAreaLayout = qobject_cast(scrollArea->widget()->layout()); detailsLB = new QLabel{qq}; detailsLB->setTextFormat(Qt::RichText); detailsLB->setTextInteractionFlags(Qt::TextSelectableByMouse); detailsLB->setWordWrap(true); labelHelper.addLabel(detailsLB); scrollAreaLayout->addWidget(detailsLB); scrollAreaLayout->addStretch(); groupBoxLayout->addWidget(scrollArea); splitter->addWidget(detailsGB); } { proposedCorrectiveActionGB = new QGroupBox{i18nc("@title:group", "Proposed Corrective Action"), qq}; auto groupBoxLayout = new QVBoxLayout{proposedCorrectiveActionGB}; auto scrollArea = new Kleo::ScrollArea{qq}; scrollArea->setFocusPolicy(Qt::NoFocus); scrollArea->setMinimumHeight(100); scrollArea->setFrameShape(QFrame::NoFrame); scrollArea->setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff); auto scrollAreaLayout = qobject_cast(scrollArea->widget()->layout()); proposedCorrectiveActionLB = new QLabel{qq}; proposedCorrectiveActionLB->setTextFormat(Qt::RichText); proposedCorrectiveActionLB->setTextInteractionFlags(Qt::TextSelectableByMouse); proposedCorrectiveActionLB->setWordWrap(true); labelHelper.addLabel(proposedCorrectiveActionLB); scrollAreaLayout->addWidget(proposedCorrectiveActionLB); scrollAreaLayout->addStretch(); groupBoxLayout->addWidget(scrollArea); { auto hbox = new QHBoxLayout; hbox->addStretch(); doItPB = new QPushButton{i18nc("@action:button", "Do It"), qq}; doItPB->setEnabled(false); hbox->addWidget(doItPB); groupBoxLayout->addLayout(hbox); } splitter->addWidget(proposedCorrectiveActionGB); } mainLayout->addWidget(splitter); showAllCB = new QCheckBox{i18nc("@option:check", "Show all test results"), qq}; showAllCB->setChecked(true); mainLayout->addWidget(showAllCB); runAtStartUpCB = new QCheckBox{i18nc("@option:check", "Run these tests at startup"), qq}; runAtStartUpCB->setChecked(true); mainLayout->addWidget(runAtStartUpCB); buttonBox = new QDialogButtonBox{qq}; buttonBox->setStandardButtons(QDialogButtonBox::Cancel|QDialogButtonBox::Close|QDialogButtonBox::Ok); buttonBox->button(QDialogButtonBox::Ok)->setText(i18nc("@action:button", "Continue")); rerunPB = buttonBox->addButton(i18nc("@action:button", "Rerun Tests"), QDialogButtonBox::ActionRole); mainLayout->addWidget(buttonBox); } } ui; }; SelfTestDialog::SelfTestDialog(QWidget *p, Qt::WindowFlags f) : QDialog(p, f), d(new Private(this)) { setWindowTitle(i18nc("@title:window", "Self Test")); resize(448, 610); setAutomaticMode(false); } SelfTestDialog::~SelfTestDialog() = default; -void SelfTestDialog::clear() +void SelfTestDialog::setTests(const std::vector> &tests) { d->model.clear(); -} - -void SelfTestDialog::addSelfTest(const std::shared_ptr &test) -{ - d->model.append(std::vector>(1, test)); - d->updateColumnSizes(); -} - -void SelfTestDialog::addSelfTests(const std::vector> &tests) -{ d->model.append(tests); d->updateColumnSizes(); } void SelfTestDialog::setRunAtStartUp(bool on) { d->ui.runAtStartUpCB->setChecked(on); } bool SelfTestDialog::runAtStartUp() const { return d->ui.runAtStartUpCB->isChecked(); } void SelfTestDialog::setAutomaticMode(bool automatic) { d->ui.showAllCB->setChecked(!automatic); d->ui.buttonBox->button(QDialogButtonBox::Ok)->setVisible(automatic); d->ui.buttonBox->button(QDialogButtonBox::Cancel)->setVisible(automatic); d->ui.buttonBox->button(QDialogButtonBox::Close)->setVisible(!automatic); } #include "selftestdialog.moc" #include "moc_selftestdialog.cpp" diff --git a/src/dialogs/selftestdialog.h b/src/dialogs/selftestdialog.h index fb34619f5..d7277db40 100644 --- a/src/dialogs/selftestdialog.h +++ b/src/dialogs/selftestdialog.h @@ -1,56 +1,52 @@ /* -*- mode: c++; c-basic-offset:4 -*- dialogs/selftestdialog.h This file is part of Kleopatra, the KDE keymanager SPDX-FileCopyrightText: 2008 Klarälvdalens Datakonsult AB SPDX-License-Identifier: GPL-2.0-or-later */ #pragma once #include #include #include #include namespace Kleo { class SelfTest; namespace Dialogs { class SelfTestDialog : public QDialog { Q_OBJECT Q_PROPERTY(bool runAtStartUp READ runAtStartUp WRITE setRunAtStartUp) public: explicit SelfTestDialog(QWidget *parent = nullptr, Qt::WindowFlags f = {}); ~SelfTestDialog() override; void setAutomaticMode(bool automatic); - void addSelfTest(const std::shared_ptr &test); - void addSelfTests(const std::vector> &tests); + void setTests(const std::vector> &tests); void setRunAtStartUp(bool run); bool runAtStartUp() const; -public Q_SLOTS: - void clear(); - Q_SIGNALS: void updateRequested(); private: class Private; kdtools::pimpl_ptr d; }; } }