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 }