File indexing completed on 2024-06-23 05:13:54

0001 /* -*- mode: c++; c-basic-offset:4 -*-
0002     crypto/gui/verifychecksumsdialog.cpp
0003 
0004     This file is part of Kleopatra, the KDE keymanager
0005     SPDX-FileCopyrightText: 2010 Klarälvdalens Datakonsult AB
0006 
0007     SPDX-License-Identifier: GPL-2.0-or-later
0008 */
0009 
0010 #include <config-kleopatra.h>
0011 
0012 #include "verifychecksumsdialog.h"
0013 
0014 #ifndef QT_NO_DIRMODEL
0015 
0016 #include <Libkleo/SystemInfo>
0017 
0018 #include <KLocalizedString>
0019 #include <KMessageBox>
0020 
0021 #include "kleopatra_debug.h"
0022 #include <QDialogButtonBox>
0023 #include <QFileSystemModel>
0024 #include <QHBoxLayout>
0025 #include <QHash>
0026 #include <QHeaderView>
0027 #include <QLabel>
0028 #include <QProgressBar>
0029 #include <QPushButton>
0030 #include <QSortFilterProxyModel>
0031 #include <QStringList>
0032 #include <QTreeView>
0033 #include <QVBoxLayout>
0034 
0035 using namespace Kleo;
0036 using namespace Kleo::Crypto;
0037 using namespace Kleo::Crypto::Gui;
0038 
0039 namespace
0040 {
0041 
0042 static Qt::GlobalColor statusColor[] = {
0043     Qt::color0, // Unknown - nothing
0044     Qt::green, // OK
0045     Qt::red, // Failed
0046     Qt::darkRed, // Error
0047 };
0048 static_assert((sizeof(statusColor) / sizeof(*statusColor)) == VerifyChecksumsDialog::NumStatii, "");
0049 
0050 class ColorizedFileSystemModel : public QFileSystemModel
0051 {
0052     Q_OBJECT
0053 public:
0054     explicit ColorizedFileSystemModel(QObject *parent = nullptr)
0055         : QFileSystemModel(parent)
0056         , statusMap()
0057     {
0058     }
0059 
0060     QVariant data(const QModelIndex &mi, int role = Qt::DisplayRole) const override
0061     {
0062         if (mi.isValid() && role == Qt::BackgroundRole && !SystemInfo::isHighContrastModeActive()) {
0063             const QHash<QString, VerifyChecksumsDialog::Status>::const_iterator it = statusMap.find(filePath(mi));
0064             if (it != statusMap.end())
0065                 if (const Qt::GlobalColor c = statusColor[*it]) {
0066                     return QColor(c);
0067                 }
0068         }
0069         return QFileSystemModel::data(mi, role);
0070     }
0071 
0072 public Q_SLOTS:
0073     void setStatus(const QString &file, VerifyChecksumsDialog::Status status)
0074     {
0075         if (status >= VerifyChecksumsDialog::NumStatii || file.isEmpty()) {
0076             return;
0077         }
0078 
0079         // canonicalize filename:
0080         const QModelIndex mi = index(file);
0081         const QString canonical = filePath(mi);
0082         if (canonical.isEmpty()) {
0083             qCDebug(KLEOPATRA_LOG) << "can't locate file " << file;
0084             return;
0085         }
0086 
0087         const QHash<QString, VerifyChecksumsDialog::Status>::iterator it = statusMap.find(canonical);
0088 
0089         if (it != statusMap.end())
0090             if (*it == status) {
0091                 return; // nothing to do
0092             } else {
0093                 *it = status;
0094             }
0095         else {
0096             statusMap[canonical] = status;
0097         }
0098 
0099         emitDataChangedFor(mi);
0100     }
0101 
0102     void clearStatusInformation()
0103     {
0104         using std::swap;
0105 
0106         QHash<QString, VerifyChecksumsDialog::Status> oldStatusMap;
0107         swap(statusMap, oldStatusMap);
0108 
0109         for (QHash<QString, VerifyChecksumsDialog::Status>::const_iterator it = oldStatusMap.constBegin(), end = oldStatusMap.constEnd(); it != end; ++it) {
0110             emitDataChangedFor(it.key());
0111         }
0112     }
0113 
0114 private:
0115     void emitDataChangedFor(const QString &file)
0116     {
0117         emitDataChangedFor(index(file));
0118     }
0119     void emitDataChangedFor(const QModelIndex &mi)
0120     {
0121         const QModelIndex p = parent(mi);
0122         Q_EMIT dataChanged(index(mi.row(), 0, p), index(mi.row(), columnCount(p) - 1, p));
0123     }
0124 
0125 private:
0126     QHash<QString, VerifyChecksumsDialog::Status> statusMap;
0127 };
0128 
0129 static int find_layout_item(const QBoxLayout &blay)
0130 {
0131     for (int i = 0, end = blay.count(); i < end; ++i)
0132         if (QLayoutItem *item = blay.itemAt(i))
0133             if (item->layout()) {
0134                 return i;
0135             }
0136     return 0;
0137 }
0138 
0139 struct BaseWidget {
0140     QSortFilterProxyModel proxy;
0141     QLabel label;
0142     QTreeView view;
0143 
0144     BaseWidget(QFileSystemModel *model, QWidget *parent, QVBoxLayout *vlay)
0145         : proxy()
0146         , label(parent)
0147         , view(parent)
0148     {
0149         KDAB_SET_OBJECT_NAME(proxy);
0150         KDAB_SET_OBJECT_NAME(label);
0151         KDAB_SET_OBJECT_NAME(view);
0152 
0153         const int row = find_layout_item(*vlay);
0154         vlay->insertWidget(row, &label);
0155         vlay->insertWidget(row + 1, &view, 1);
0156 
0157         proxy.setSourceModel(model);
0158 
0159         view.setModel(&proxy);
0160 
0161         QRect r;
0162         for (int i = 0; i < proxy.columnCount(); ++i) {
0163             view.resizeColumnToContents(i);
0164         }
0165 
0166         // define some minimum sizes
0167         view.header()->resizeSection(0, qMax(view.header()->sectionSize(0), 220));
0168         view.header()->resizeSection(1, qMax(view.header()->sectionSize(1), 75));
0169         view.header()->resizeSection(2, qMax(view.header()->sectionSize(2), 75));
0170         view.header()->resizeSection(3, qMax(view.header()->sectionSize(3), 140));
0171 
0172         for (int i = 0; i < proxy.rowCount(); ++i) {
0173             r = r.united(view.visualRect(proxy.index(proxy.columnCount() - 1, i)));
0174         }
0175         view.setMinimumSize(
0176             QSize(qBound(r.width() + 4 * view.frameWidth(), 220 + 75 + 75 + 140 + 4 * view.frameWidth(), 1024), // 100 is the default defaultSectionSize
0177                   qBound(r.height(), 220, 512)));
0178     }
0179 
0180     void setBase(const QString &base)
0181     {
0182         label.setText(base);
0183         if (auto fsm = qobject_cast<QFileSystemModel *>(proxy.sourceModel())) {
0184             view.setRootIndex(proxy.mapFromSource(fsm->index(base)));
0185         } else {
0186             qCWarning(KLEOPATRA_LOG) << "expect a QFileSystemModel-derived class as proxy.sourceModel(), got ";
0187             if (!proxy.sourceModel()) {
0188                 qCWarning(KLEOPATRA_LOG) << "a null pointer";
0189             } else {
0190                 qCWarning(KLEOPATRA_LOG) << proxy.sourceModel()->metaObject()->className();
0191             }
0192         }
0193     }
0194 };
0195 
0196 } // anon namespace
0197 
0198 class VerifyChecksumsDialog::Private
0199 {
0200     friend class ::Kleo::Crypto::Gui::VerifyChecksumsDialog;
0201     VerifyChecksumsDialog *const q;
0202 
0203 public:
0204     explicit Private(VerifyChecksumsDialog *qq)
0205         : q(qq)
0206         , bases()
0207         , errors()
0208         , model()
0209         , ui(q)
0210     {
0211         qRegisterMetaType<Status>("Kleo::Crypto::Gui::VerifyChecksumsDialog::Status");
0212     }
0213 
0214 private:
0215     void slotErrorButtonClicked()
0216     {
0217         KMessageBox::errorList(q, i18n("The following errors and warnings were recorded:"), errors, i18nc("@title:window", "Checksum Verification Errors"));
0218     }
0219 
0220 private:
0221     void updateErrors()
0222     {
0223         const bool active = ui.isProgressBarActive();
0224         ui.progressLabel.setVisible(active);
0225         ui.progressBar.setVisible(active);
0226         ui.errorLabel.setVisible(!active);
0227         ui.errorButton.setVisible(!active && !errors.empty());
0228         if (errors.empty()) {
0229             ui.errorLabel.setText(i18n("No errors occurred"));
0230         } else {
0231             ui.errorLabel.setText(i18np("One error occurred", "%1 errors occurred", errors.size()));
0232         }
0233     }
0234 
0235 private:
0236     QStringList bases;
0237     QStringList errors;
0238     ColorizedFileSystemModel model;
0239 
0240     struct UI {
0241         std::vector<BaseWidget *> baseWidgets;
0242         QLabel progressLabel;
0243         QProgressBar progressBar;
0244         QLabel errorLabel;
0245         QPushButton errorButton;
0246         QDialogButtonBox buttonBox;
0247         QVBoxLayout vlay;
0248         QHBoxLayout hlay[2];
0249 
0250         explicit UI(VerifyChecksumsDialog *q)
0251             : baseWidgets()
0252             , progressLabel(i18n("Progress:"), q)
0253             , progressBar(q)
0254             , errorLabel(i18n("No errors occurred"), q)
0255             , errorButton(i18nc("Show Errors", "Show"), q)
0256             , buttonBox(QDialogButtonBox::Close, Qt::Horizontal, q)
0257             , vlay(q)
0258         {
0259             KDAB_SET_OBJECT_NAME(progressLabel);
0260             KDAB_SET_OBJECT_NAME(progressBar);
0261             KDAB_SET_OBJECT_NAME(errorLabel);
0262             KDAB_SET_OBJECT_NAME(errorButton);
0263             KDAB_SET_OBJECT_NAME(buttonBox);
0264             KDAB_SET_OBJECT_NAME(vlay);
0265             KDAB_SET_OBJECT_NAME(hlay[0]);
0266             KDAB_SET_OBJECT_NAME(hlay[1]);
0267 
0268             errorButton.setAutoDefault(false);
0269 
0270             hlay[0].addWidget(&progressLabel);
0271             hlay[0].addWidget(&progressBar, 1);
0272 
0273             hlay[1].addWidget(&errorLabel, 1);
0274             hlay[1].addWidget(&errorButton);
0275 
0276             vlay.addLayout(&hlay[0]);
0277             vlay.addLayout(&hlay[1]);
0278             vlay.addWidget(&buttonBox);
0279 
0280             errorLabel.hide();
0281             errorButton.hide();
0282 
0283             QPushButton *close = closeButton();
0284 
0285             connect(close, &QPushButton::clicked, q, &VerifyChecksumsDialog::canceled);
0286             connect(close, &QPushButton::clicked, q, &VerifyChecksumsDialog::accept);
0287 
0288             connect(&errorButton, SIGNAL(clicked()), q, SLOT(slotErrorButtonClicked()));
0289         }
0290 
0291         ~UI()
0292         {
0293             qDeleteAll(baseWidgets);
0294         }
0295 
0296         QPushButton *closeButton() const
0297         {
0298             return buttonBox.button(QDialogButtonBox::Close);
0299         }
0300 
0301         void setBases(const QStringList &bases, QFileSystemModel *model)
0302         {
0303             // create new BaseWidgets:
0304             for (unsigned int i = baseWidgets.size(), end = bases.size(); i < end; ++i) {
0305                 baseWidgets.push_back(new BaseWidget(model, vlay.parentWidget(), &vlay));
0306             }
0307 
0308             // shed surplus BaseWidgets:
0309             for (unsigned int i = bases.size(), end = baseWidgets.size(); i < end; ++i) {
0310                 delete baseWidgets.back();
0311                 baseWidgets.pop_back();
0312             }
0313 
0314             Q_ASSERT(static_cast<unsigned>(bases.size()) == baseWidgets.size());
0315 
0316             // update bases:
0317             for (unsigned int i = 0; i < baseWidgets.size(); ++i) {
0318                 baseWidgets[i]->setBase(bases[i]);
0319             }
0320         }
0321 
0322         void setProgress(int cur, int tot)
0323         {
0324             progressBar.setMaximum(tot);
0325             progressBar.setValue(cur);
0326         }
0327 
0328         bool isProgressBarActive() const
0329         {
0330             const int tot = progressBar.maximum();
0331             const int cur = progressBar.value();
0332             return !tot || cur != tot;
0333         }
0334 
0335     } ui;
0336 };
0337 
0338 VerifyChecksumsDialog::VerifyChecksumsDialog(QWidget *parent)
0339     : QDialog(parent)
0340     , d(new Private(this))
0341 {
0342 }
0343 
0344 VerifyChecksumsDialog::~VerifyChecksumsDialog()
0345 {
0346 }
0347 
0348 // slot
0349 void VerifyChecksumsDialog::setBaseDirectories(const QStringList &bases)
0350 {
0351     if (d->bases == bases) {
0352         return;
0353     }
0354     d->bases = bases;
0355     d->ui.setBases(bases, &d->model);
0356 }
0357 
0358 // slot
0359 void VerifyChecksumsDialog::setErrors(const QStringList &errors)
0360 {
0361     if (d->errors == errors) {
0362         return;
0363     }
0364     d->errors = errors;
0365     d->updateErrors();
0366 }
0367 
0368 // slot
0369 void VerifyChecksumsDialog::setProgress(int cur, int tot)
0370 {
0371     d->ui.setProgress(cur, tot);
0372     d->updateErrors();
0373 }
0374 
0375 // slot
0376 void VerifyChecksumsDialog::setStatus(const QString &file, Status status)
0377 {
0378     d->model.setStatus(file, status);
0379 }
0380 
0381 // slot
0382 void VerifyChecksumsDialog::clearStatusInformation()
0383 {
0384     d->errors.clear();
0385     d->updateErrors();
0386     d->model.clearStatusInformation();
0387 }
0388 
0389 #include "moc_verifychecksumsdialog.cpp"
0390 #include "verifychecksumsdialog.moc"
0391 
0392 #endif // QT_NO_DIRMODEL