File indexing completed on 2024-12-22 04:14:55
0001 /* 0002 * SPDX-FileCopyrightText: 2020 Dmitrii Utkin <loentar@gmail.com> 0003 * 0004 * SPDX-License-Identifier: LGPL-2.1-only 0005 */ 0006 0007 0008 #include "recorder_snapshots_manager.h" 0009 #include "ui_recorder_snapshots_manager.h" 0010 #include "recorder_directory_cleaner.h" 0011 #include "recorder_const.h" 0012 0013 #include <klocalizedstring.h> 0014 0015 #include <QCloseEvent> 0016 #include <QDebug> 0017 #include <QDir> 0018 #include <QLocale> 0019 #include <QMessageBox> 0020 #include <QStandardItemModel> 0021 #include <QDialogButtonBox> 0022 0023 namespace 0024 { 0025 #if (QT_VERSION < QT_VERSION_CHECK(5, 10, 0)) 0026 // FIXME This is to support Ubuntu 16.04. Remove this after dropping support of Qt < 5.10. 0027 QString qlocale_formattedDataSize(qint64 bytes) 0028 { 0029 static QStringList suffixes = {"B", "KiB", "MiB", "GiB", "TiB"}; 0030 constexpr const qreal divider = 1024; 0031 0032 int index = 0; 0033 qreal size = bytes; 0034 0035 while (size > divider && index < suffixes.length()) { 0036 size /= divider; 0037 ++index; 0038 } 0039 0040 return QString("%1 %2").arg(size, 0, 'f', 1).arg(suffixes[index]); 0041 } 0042 #endif 0043 0044 0045 constexpr int valueRole = Qt::UserRole + 1; 0046 constexpr int defaultColumnMargin = 16; 0047 0048 enum Page 0049 { 0050 PageProgress = 0, 0051 PageSelection = 1 0052 }; 0053 0054 enum Column 0055 { 0056 ColumnCheck = 0, 0057 ColumnName = 1, 0058 ColumnSize = 2, 0059 ColumnDate = 3, 0060 ColumnCount = ColumnDate + 1 0061 }; 0062 0063 class DataSortedItem : public QStandardItem 0064 { 0065 public: 0066 DataSortedItem(const QString &text, qulonglong sortValue) 0067 : QStandardItem(text) 0068 { 0069 setData(sortValue, valueRole); 0070 } 0071 0072 bool operator<(const QStandardItem &other) const override 0073 { 0074 return data(valueRole).toULongLong() < other.data(valueRole).toULongLong(); 0075 } 0076 }; 0077 0078 class CheckedIconItem : public QStandardItem 0079 { 0080 public: 0081 CheckedIconItem(const QString &imagePath, const QSize &iconSize) 0082 { 0083 setData(Qt::Unchecked, Qt::CheckStateRole); 0084 0085 QPixmap pixmap(imagePath); 0086 const int minSide = qMin(pixmap.width(), pixmap.height()); 0087 setIcon(pixmap.copy((pixmap.width() - minSide) / 2, (pixmap.height() - minSide) / 2, minSide, minSide) 0088 .scaled(iconSize, Qt::KeepAspectRatioByExpanding, Qt::SmoothTransformation)); 0089 } 0090 }; 0091 0092 } 0093 0094 RecorderSnapshotsManager::RecorderSnapshotsManager(QWidget *parent) 0095 : QDialog(parent) 0096 , ui(new Ui::RecorderSnapshotsManager) 0097 , scanner(new RecorderSnapshotsScanner) 0098 , cleaner(nullptr) 0099 { 0100 ui->setupUi(this); 0101 0102 ui->stackedWidget->setCurrentIndex(PageProgress); 0103 0104 connect(scanner, SIGNAL(scanningFinished(SnapshotDirInfoList)), 0105 this, SLOT(onScanningFinished(SnapshotDirInfoList))); 0106 connect(ui->buttonSelectAll, SIGNAL(clicked()), this, SLOT(onButtonSelectAllClicked())); 0107 connect(ui->buttonBox->button(QDialogButtonBox::Discard), SIGNAL(clicked()), this, SLOT(onButtonCleanUpClicked())); 0108 connect(ui->buttonBox, &QDialogButtonBox::rejected, this, &QDialog::reject); 0109 } 0110 0111 RecorderSnapshotsManager::~RecorderSnapshotsManager() 0112 { 0113 delete scanner; 0114 delete ui; 0115 } 0116 0117 void RecorderSnapshotsManager::execFor(const QString &snapshotsDirectory) 0118 { 0119 scanner->setup(snapshotsDirectory); 0120 startScanning(); 0121 exec(); 0122 } 0123 0124 void RecorderSnapshotsManager::closeEvent(QCloseEvent *event) 0125 { 0126 abortCleanUp(); 0127 QDialog::closeEvent(event); 0128 } 0129 0130 void RecorderSnapshotsManager::reject() 0131 { 0132 abortCleanUp(); 0133 QDialog::reject(); 0134 } 0135 0136 void RecorderSnapshotsManager::onScanningFinished(SnapshotDirInfoList snapshots) 0137 { 0138 QStandardItemModel *model = new QStandardItemModel(this); 0139 model->setColumnCount(ColumnCount); 0140 0141 QLocale locale = this->locale(); 0142 const QString &dateFormat = locale.dateTimeFormat(QLocale::ShortFormat); 0143 for (const SnapshotDirInfo &info : snapshots) { 0144 QStandardItem *nameCol = new QStandardItem(info.name); 0145 nameCol->setData(info.path, valueRole); 0146 model->appendRow({ 0147 new CheckedIconItem(info.thumbnail, ui->treeDirectories->iconSize()), 0148 nameCol, 0149 #if (QT_VERSION >= QT_VERSION_CHECK(5, 10, 0)) 0150 new DataSortedItem(locale.formattedDataSize(info.size), info.size), 0151 #else 0152 new DataSortedItem(qlocale_formattedDataSize(info.size), info.size), 0153 #endif 0154 new DataSortedItem(info.dateTime.toString(dateFormat), info.dateTime.toMSecsSinceEpoch()) 0155 }); 0156 } 0157 0158 model->setHorizontalHeaderItem(ColumnCheck, 0159 new QStandardItem(i18nc("Header title for preview thumbnail", "Preview"))); 0160 model->setHorizontalHeaderItem(ColumnName, 0161 new QStandardItem(i18nc("Header title for directory name column", "Name"))); 0162 model->setHorizontalHeaderItem(ColumnSize, 0163 new QStandardItem(i18nc("Header title for size of directory column", "Size"))); 0164 model->setHorizontalHeaderItem(ColumnDate, 0165 new QStandardItem(i18nc("Header title for last modified date/time column", "Last Modified"))); 0166 0167 QAbstractItemModel *oldModel = ui->treeDirectories->model(); 0168 QItemSelectionModel *oldSelectionModel = ui->treeDirectories->selectionModel(); 0169 ui->treeDirectories->setModel(model); 0170 if (oldModel != nullptr) 0171 oldModel->deleteLater(); 0172 if (oldSelectionModel != nullptr) 0173 oldSelectionModel->deleteLater(); 0174 0175 ui->buttonBox->button(QDialogButtonBox::Discard)->setEnabled(false); 0176 ui->stackedWidget->setCurrentIndex(PageSelection); 0177 0178 for(int col = 0; col < (ColumnCount - 1); ++col) { 0179 ui->treeDirectories->resizeColumnToContents(col); 0180 const int colWidth = ui->treeDirectories->columnWidth(col); 0181 ui->treeDirectories->setColumnWidth(col, colWidth + defaultColumnMargin); 0182 } 0183 0184 connect(ui->treeDirectories->selectionModel(), SIGNAL(selectionChanged(QItemSelection,QItemSelection)), 0185 this, SLOT(onSelectionChanged(QItemSelection,QItemSelection)), Qt::UniqueConnection); 0186 } 0187 0188 void RecorderSnapshotsManager::onSelectionChanged(const QItemSelection &selected, const QItemSelection &deselected) 0189 { 0190 QAbstractItemModel *model = ui->treeDirectories->model(); 0191 for (const QModelIndex &index : selected.indexes()) 0192 model->setData(index.sibling(index.row(), ColumnCheck), Qt::Checked, Qt::CheckStateRole); 0193 for (const QModelIndex &index : deselected.indexes()) 0194 model->setData(index.sibling(index.row(), ColumnCheck), Qt::Unchecked, Qt::CheckStateRole); 0195 0196 ui->buttonBox->button(QDialogButtonBox::Discard)->setEnabled(!ui->treeDirectories->selectionModel()->selectedIndexes().isEmpty()); 0197 0198 updateSpaceToBeFreed(); 0199 } 0200 0201 void RecorderSnapshotsManager::startScanning() 0202 { 0203 ui->labelProgress->setText(i18nc("Label title, Scanning for directory, files, etc..", "Scanning...")); 0204 ui->stackedWidget->setCurrentIndex(PageProgress); 0205 scanner->start(); 0206 } 0207 0208 void RecorderSnapshotsManager::updateSpaceToBeFreed() 0209 { 0210 const QModelIndexList &indexes = ui->treeDirectories->selectionModel()->selectedRows(ColumnSize); 0211 qulonglong totalSize = 0; 0212 QAbstractItemModel *model = ui->treeDirectories->model(); 0213 for (const QModelIndex &index : indexes) 0214 totalSize += model->data(index, valueRole).toULongLong(); 0215 0216 #if (QT_VERSION >= QT_VERSION_CHECK(5, 10, 0)) 0217 ui->labelSpace->setText(locale().formattedDataSize(totalSize)); 0218 #else 0219 ui->labelSpace->setText(qlocale_formattedDataSize(totalSize)); 0220 #endif 0221 ui->buttonSelectAll->setText(indexes.size() != model->rowCount() ? i18n("Select All") : i18n("Select None")); 0222 } 0223 0224 void RecorderSnapshotsManager::abortCleanUp() 0225 { 0226 if (cleaner == nullptr) 0227 return; 0228 0229 cleaner->stop(); 0230 cleaner->deleteLater(); 0231 cleaner = nullptr; 0232 } 0233 0234 void RecorderSnapshotsManager::onButtonSelectAllClicked() 0235 { 0236 const QModelIndexList &selectedIndexes = ui->treeDirectories->selectionModel()->selectedRows(ColumnCheck); 0237 QAbstractItemModel *model = ui->treeDirectories->model(); 0238 int rowCount = model->rowCount(); 0239 bool selectAll = selectedIndexes.size() != rowCount; 0240 if (selectAll) { 0241 ui->treeDirectories->selectAll(); 0242 } else { 0243 ui->treeDirectories->clearSelection(); 0244 } 0245 } 0246 0247 void RecorderSnapshotsManager::onButtonCleanUpClicked() 0248 { 0249 const QString confirmation(i18n("The selected recordings will be deleted" 0250 " and you will not be able to export a timelapse for them again" 0251 " (the already exported timelapses will be preserved though)." 0252 "\nDo you wish to continue?")); 0253 if (QMessageBox::question(this, windowTitle(), confirmation) != QMessageBox::Yes) 0254 return; 0255 0256 QStringList directories; 0257 const QModelIndexList &indexes = ui->treeDirectories->selectionModel()->selectedRows(ColumnName); 0258 QAbstractItemModel *model = ui->treeDirectories->model(); 0259 for (const QModelIndex &index : indexes) 0260 directories.append(model->data(index, valueRole).toString()); 0261 0262 ui->labelProgress->setText(i18nc("Label title, Snapshot directory deleting is in progress", "Cleaning up...")); 0263 ui->stackedWidget->setCurrentIndex(PageProgress); 0264 0265 Q_ASSERT(cleaner == nullptr); 0266 cleaner = new RecorderDirectoryCleaner(directories); 0267 connect(cleaner, SIGNAL(finished()), this, SLOT(onCleanUpFinished())); 0268 cleaner->start(); 0269 } 0270 0271 void RecorderSnapshotsManager::onButtonCancelCleanUpClicked() 0272 { 0273 abortCleanUp(); 0274 startScanning(); 0275 } 0276 0277 void RecorderSnapshotsManager::onCleanUpFinished() 0278 { 0279 cleaner->deleteLater(); 0280 cleaner = nullptr; 0281 0282 startScanning(); 0283 }