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